From a3ee13d767b0bcd1c7acc9f950a66e355a3b26d3 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Mon, 28 Jan 2019 11:04:05 +0100 Subject: [PATCH] Add hashtag autocompletion, closes #769 (#1001) * Add hashtag autocompletion, closes #769 * Apply review feedback --- .../keylesspalace/tusky/ComposeActivity.java | 94 +++++--- .../adapter/MentionAutoCompleteAdapter.java | 143 ----------- .../MentionTagAutoCompleteAdapter.java | 223 ++++++++++++++++++ ...ionTokenizer.kt => MentionTagTokenizer.kt} | 10 +- ...lete.xml => item_autocomplete_account.xml} | 0 .../tusky/MentionTagTokenizerTest.kt | 65 +++++ .../tusky/MentionTokenizerTest.kt | 61 ----- 7 files changed, 352 insertions(+), 244 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/MentionAutoCompleteAdapter.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/MentionTagAutoCompleteAdapter.java rename app/src/main/java/com/keylesspalace/tusky/util/{MentionTokenizer.kt => MentionTagTokenizer.kt} (88%) rename app/src/main/res/layout/{item_autocomplete.xml => item_autocomplete_account.xml} (100%) create mode 100644 app/src/test/java/com/keylesspalace/tusky/MentionTagTokenizerTest.kt delete mode 100644 app/src/test/java/com/keylesspalace/tusky/MentionTokenizerTest.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 20a1371a..02c16dd5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -18,7 +18,6 @@ package com.keylesspalace.tusky; import android.Manifest; import android.annotation.SuppressLint; import android.app.ProgressDialog; -import androidx.lifecycle.Lifecycle; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -39,25 +38,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.preference.PreferenceManager; import android.provider.MediaStore; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.Px; -import androidx.annotation.StringRes; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.snackbar.Snackbar; -import androidx.transition.TransitionManager; -import androidx.core.view.inputmethod.InputConnectionCompat; -import androidx.core.view.inputmethod.InputContentInfoCompat; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; @@ -81,10 +61,12 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.snackbar.Snackbar; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.keylesspalace.tusky.adapter.EmojiAdapter; -import com.keylesspalace.tusky.adapter.MentionAutoCompleteAdapter; +import com.keylesspalace.tusky.adapter.MentionTagAutoCompleteAdapter; import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AppDatabase; @@ -94,6 +76,7 @@ import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Emoji; import com.keylesspalace.tusky.entity.Instance; +import com.keylesspalace.tusky.entity.SearchResults; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.ProgressRequestBody; @@ -101,7 +84,7 @@ import com.keylesspalace.tusky.service.SendTootService; import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.DownsizeImageTask; import com.keylesspalace.tusky.util.ListUtils; -import com.keylesspalace.tusky.util.MentionTokenizer; +import com.keylesspalace.tusky.util.MentionTagTokenizer; import com.keylesspalace.tusky.util.SaveTootHelper; import com.keylesspalace.tusky.util.SpanUtilsKt; import com.keylesspalace.tusky.util.StringUtils; @@ -131,12 +114,31 @@ import java.util.Locale; import javax.inject.Inject; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.core.view.inputmethod.InputConnectionCompat; +import androidx.core.view.inputmethod.InputContentInfoCompat; +import androidx.lifecycle.Lifecycle; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.TransitionManager; import at.connyduck.sparkbutton.helpers.Utils; import io.reactivex.Single; import io.reactivex.SingleObserver; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import kotlin.collections.CollectionsKt; import okhttp3.MediaType; import okhttp3.MultipartBody; import retrofit2.Call; @@ -155,7 +157,7 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid public final class ComposeActivity extends BaseActivity implements ComposeOptionsListener, - MentionAutoCompleteAdapter.AccountSearchProvider, + MentionTagAutoCompleteAdapter.AutocompletionProvider, OnEmojiSelectedListener, Injectable, InputConnectionCompat.OnCommitContentListener { @@ -225,7 +227,8 @@ public final class ComposeActivity private int savedTootUid = 0; private List emojiList; private int maximumTootCharacters = STATUS_CHARACTER_LIMIT; - private @Px int thumbnailViewSize; + private @Px + int thumbnailViewSize; private SaveTootHelper saveTootHelper; private Gson gson = new Gson(); @@ -516,8 +519,8 @@ public final class ComposeActivity }); textEditor.setAdapter( - new MentionAutoCompleteAdapter(this, R.layout.item_autocomplete, this)); - textEditor.setTokenizer(new MentionTokenizer()); + new MentionTagAutoCompleteAdapter(this)); + textEditor.setTokenizer(new MentionTagTokenizer()); // Add any mentions to the text field when a reply is first composed. if (mentionedUsernames != null) { @@ -983,7 +986,7 @@ public final class ComposeActivity spoilerText = contentWarningEditor.getText().toString(); } int characterCount = calculateTextLength(); - if (characterCount <= 0 && mediaQueued.size()==0) { + if (characterCount <= 0 && mediaQueued.size() == 0) { textEditor.setError(getString(R.string.error_empty)); enableButtons(); } else if (characterCount <= maximumTootCharacters) { @@ -1532,19 +1535,38 @@ public final class ComposeActivity } @Override - public List searchAccounts(String mention) { - ArrayList resultList = new ArrayList<>(); + public List search(String token) { try { - List accountList = mastodonApi.searchAccounts(mention, false, 40) - .execute() - .body(); - if (accountList != null) { - resultList.addAll(accountList); + switch (token.charAt(0)) { + case '@': + ArrayList resultList = new ArrayList<>(); + List accountList = mastodonApi + .searchAccounts(token.substring(1), false, 20) + .execute() + .body(); + if (accountList != null) { + resultList.addAll(accountList); + } + return CollectionsKt.map(resultList, MentionTagAutoCompleteAdapter.AccountResult::new); + case '#': + Response response = mastodonApi.search(token, false).execute(); + if (response.isSuccessful() && response.body() != null) { + return CollectionsKt.map( + response.body().getHashtags(), + MentionTagAutoCompleteAdapter.HashtagResult::new + ); + } else { + Log.e(TAG, String.format("Autocomplete search for %s failed.", token)); + return Collections.emptyList(); + } + default: + Log.w(TAG, "Unexpected autocompletion token: " + token); + return Collections.emptyList(); } } catch (IOException e) { - Log.e(TAG, String.format("Autocomplete search for %s failed.", mention)); + Log.e(TAG, String.format("Autocomplete search for %s failed.", token)); + return Collections.emptyList(); } - return resultList; } @Override diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MentionAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MentionAutoCompleteAdapter.java deleted file mode 100644 index 1104975f..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MentionAutoCompleteAdapter.java +++ /dev/null @@ -1,143 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * 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. - * - * Tusky 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 Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.adapter; - -import android.content.Context; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.ImageView; -import android.widget.TextView; - -import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.entity.Account; -import com.keylesspalace.tusky.util.CustomEmojiHelper; -import com.squareup.picasso.Picasso; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by charlag on 12/11/17. - */ - -public class MentionAutoCompleteAdapter extends ArrayAdapter - implements Filterable { - private ArrayList resultList; - @LayoutRes - private int layoutId; - private final AccountSearchProvider accountSearchProvider; - - public MentionAutoCompleteAdapter(Context context, @LayoutRes int resource, - AccountSearchProvider accountSearchProvider) { - super(context, resource); - layoutId = resource; - resultList = new ArrayList<>(); - this.accountSearchProvider = accountSearchProvider; - } - - @Override - public int getCount() { - return resultList.size(); - } - - @Override - public Account getItem(int index) { - return resultList.get(index); - } - - @Override - @NonNull - public Filter getFilter() { - return new Filter() { - @Override - public CharSequence convertResultToString(Object resultValue) { - return ((Account) resultValue).getUsername(); - } - - // This method is invoked in a worker thread. - @Override - protected FilterResults performFiltering(CharSequence constraint) { - FilterResults filterResults = new FilterResults(); - if (constraint != null) { - List accounts = - accountSearchProvider.searchAccounts(constraint.toString()); - filterResults.values = accounts; - filterResults.count = accounts.size(); - } - return filterResults; - } - - @SuppressWarnings("unchecked") - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - if (results != null && results.count > 0) { - resultList.clear(); - ArrayList newResults = (ArrayList) results.values; - resultList.addAll(newResults); - notifyDataSetChanged(); - } else { - notifyDataSetInvalidated(); - } - } - }; - } - - @Override - @NonNull - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - View view = convertView; - - Context context = getContext(); - - if (convertView == null) { - LayoutInflater layoutInflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - //noinspection ConstantConditions - view = layoutInflater.inflate(layoutId, parent, false); - } - - Account account = getItem(position); - if (account != null) { - TextView username = view.findViewById(R.id.username); - TextView displayName = view.findViewById(R.id.display_name); - ImageView avatar = view.findViewById(R.id.avatar); - String format = getContext().getString(R.string.status_username_format); - String formattedUsername = String.format(format, account.getUsername()); - username.setText(formattedUsername); - CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName); - displayName.setText(emojifiedName); - if (!account.getAvatar().isEmpty()) { - Picasso.with(context) - .load(account.getAvatar()) - .placeholder(R.drawable.avatar_default) - .into(avatar); - } - } - - return view; - } - - public interface AccountSearchProvider { - List searchAccounts(String mention); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MentionTagAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MentionTagAutoCompleteAdapter.java new file mode 100644 index 00000000..fad51103 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MentionTagAutoCompleteAdapter.java @@ -0,0 +1,223 @@ +/* Copyright 2017 Andrew Dawson + * + * This file is a part of Tusky. + * + * 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. + * + * Tusky 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 Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; + +import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.entity.Account; +import com.keylesspalace.tusky.util.CustomEmojiHelper; +import com.squareup.picasso.Picasso; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Created by charlag on 12/11/17. + */ + +public class MentionTagAutoCompleteAdapter extends BaseAdapter + implements Filterable { + private static final int ACCOUNT_VIEW_TYPE = 0; + private static final int HASHTAG_VIEW_TYPE = 1; + + private final ArrayList resultList; + private final AutocompletionProvider autocompletionProvider; + + public MentionTagAutoCompleteAdapter(AutocompletionProvider autocompletionProvider) { + super(); + resultList = new ArrayList<>(); + this.autocompletionProvider = autocompletionProvider; + } + + @Override + public int getCount() { + return resultList.size(); + } + + @Override + public AutocompleteResult getItem(int index) { + return resultList.get(index); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + @NonNull + public Filter getFilter() { + return new Filter() { + @Override + public CharSequence convertResultToString(Object resultValue) { + if (resultValue instanceof AccountResult) { + return ((AccountResult) resultValue).account.getUsername(); + } else { + return formatHashtag((HashtagResult) resultValue); + } + } + + // This method is invoked in a worker thread. + @Override + protected FilterResults performFiltering(CharSequence constraint) { + FilterResults filterResults = new FilterResults(); + if (constraint != null) { + List results = + autocompletionProvider.search(constraint.toString()); + filterResults.values = results; + filterResults.count = results.size(); + } + return filterResults; + } + + @SuppressWarnings("unchecked") + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + if (results != null && results.count > 0) { + resultList.clear(); + resultList.addAll((List) results.values); + notifyDataSetChanged(); + } else { + notifyDataSetInvalidated(); + } + } + }; + } + + @Override + @NonNull + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + View view = convertView; + final Context context = parent.getContext(); + + switch (getItemViewType(position)) { + case ACCOUNT_VIEW_TYPE: + AccountViewHolder holder; + if (convertView == null) { + //noinspection ConstantConditions + view = ((LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .inflate(R.layout.item_autocomplete_account, parent, false); + } + if (view.getTag() == null) { + view.setTag(new AccountViewHolder(view)); + } + holder = (AccountViewHolder) view.getTag(); + + AccountResult accountResult = ((AccountResult) getItem(position)); + if (accountResult != null) { + Account account = accountResult.account; + String format = context.getString(R.string.status_username_format); + String formattedUsername = String.format(format, account.getUsername()); + holder.username.setText(formattedUsername); + CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), + account.getEmojis(), holder.displayName); + holder.displayName.setText(emojifiedName); + if (!account.getAvatar().isEmpty()) { + Picasso.with(context) + .load(account.getAvatar()) + .placeholder(R.drawable.avatar_default) + .into(holder.avatar); + } + } + break; + + case HASHTAG_VIEW_TYPE: + if (convertView == null) { + view = ((LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .inflate(R.layout.item_hashtag, parent, false); + } + + HashtagResult result = (HashtagResult) getItem(position); + if (result != null) { + ((TextView) view).setText(formatHashtag(result)); + } + break; + default: + throw new AssertionError("unknown view type"); + } + + return view; + } + + private String formatHashtag(HashtagResult result) { + return String.format("#%s", result.hashtag); + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + if (getItem(position) instanceof AccountResult) { + return ACCOUNT_VIEW_TYPE; + } else { + return HASHTAG_VIEW_TYPE; + } + } + + public abstract static class AutocompleteResult { + AutocompleteResult() { + } + } + + public final static class AccountResult extends AutocompleteResult { + private final Account account; + + public AccountResult(Account account) { + this.account = account; + } + } + + public final static class HashtagResult extends AutocompleteResult { + private final String hashtag; + + public HashtagResult(String hashtag) { + this.hashtag = hashtag; + } + } + + public interface AutocompletionProvider { + List search(String mention); + } + + private class AccountViewHolder { + final TextView username; + final TextView displayName; + final ImageView avatar; + + private AccountViewHolder(View view) { + username = view.findViewById(R.id.username); + displayName = view.findViewById(R.id.display_name); + avatar = view.findViewById(R.id.avatar); + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/MentionTokenizer.kt b/app/src/main/java/com/keylesspalace/tusky/util/MentionTagTokenizer.kt similarity index 88% rename from app/src/main/java/com/keylesspalace/tusky/util/MentionTokenizer.kt rename to app/src/main/java/com/keylesspalace/tusky/util/MentionTagTokenizer.kt index cb996c0e..76adc3a4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/MentionTokenizer.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/MentionTagTokenizer.kt @@ -20,14 +20,14 @@ import android.text.Spanned import android.text.TextUtils import android.widget.MultiAutoCompleteTextView -class MentionTokenizer : MultiAutoCompleteTextView.Tokenizer { +class MentionTagTokenizer : MultiAutoCompleteTextView.Tokenizer { override fun findTokenStart(text: CharSequence, cursor: Int): Int { if (cursor == 0) { return cursor } var i = cursor var character = text[i - 1] - while (i > 0 && character != '@') { + while (i > 0 && character != '@' && character != '#') { // See SpanUtils.MENTION_REGEX if (!Character.isLetterOrDigit(character) && character != '_') { return cursor @@ -35,10 +35,12 @@ class MentionTokenizer : MultiAutoCompleteTextView.Tokenizer { i-- character = if (i == 0) ' ' else text[i - 1] } - if (i < 1 || character != '@') { + if (i < 1 + || (character != '@' && character != '#') + || i > 1 && !Character.isWhitespace(text[i - 2])) { return cursor } - return i + return i - 1 } override fun findTokenEnd(text: CharSequence, cursor: Int): Int { diff --git a/app/src/main/res/layout/item_autocomplete.xml b/app/src/main/res/layout/item_autocomplete_account.xml similarity index 100% rename from app/src/main/res/layout/item_autocomplete.xml rename to app/src/main/res/layout/item_autocomplete_account.xml diff --git a/app/src/test/java/com/keylesspalace/tusky/MentionTagTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/MentionTagTokenizerTest.kt new file mode 100644 index 00000000..7e41819f --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/MentionTagTokenizerTest.kt @@ -0,0 +1,65 @@ +/* Copyright 2018 Levi Bard + * + * This file is a part of Tusky. + * + * 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. + * + * Tusky 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 Tusky; if not, + * see . */ + +package com.keylesspalace.tusky + +import com.keylesspalace.tusky.util.MentionTagTokenizer +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class MentionTagTokenizerTest(private val text: CharSequence, + private val expectedStartIndex: Int, + private val expectedEndIndex: Int) { + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun data(): Iterable { + return listOf( + arrayOf("@mention", 0, 8), + arrayOf("@ment10n", 0, 8), + arrayOf("@ment10n_", 0, 9), + arrayOf("@ment10n_n", 0, 10), + arrayOf("@ment10n_9", 0, 10), + arrayOf(" @mention", 1, 9), + arrayOf(" @ment10n", 1, 9), + arrayOf(" @ment10n_", 1, 10), + arrayOf(" @ment10n_ @", 11, 12), + arrayOf(" @ment10n_ @ment20n", 11, 19), + arrayOf(" @ment10n_ @ment20n_", 11, 20), + arrayOf(" @ment10n_ @ment20n_n", 11, 21), + arrayOf(" @ment10n_ @ment20n_9", 11, 21), + arrayOf("mention", 7, 7), + arrayOf("ment10n", 7, 7), + arrayOf("mentio_", 7, 7), + arrayOf("#tusky", 0, 6), + arrayOf("#@tusky", 7, 7), + arrayOf("@#tusky", 7, 7), + arrayOf(" @#tusky", 8, 8) + ) + } + } + + private val tokenizer = MentionTagTokenizer() + + @Test + fun tokenIndices_matchExpectations() { + Assert.assertEquals(expectedStartIndex, tokenizer.findTokenStart(text, text.length)) + Assert.assertEquals(expectedEndIndex, tokenizer.findTokenEnd(text, text.length)) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/keylesspalace/tusky/MentionTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/MentionTokenizerTest.kt deleted file mode 100644 index dbcdc53f..00000000 --- a/app/src/test/java/com/keylesspalace/tusky/MentionTokenizerTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright 2018 Levi Bard - * - * This file is a part of Tusky. - * - * 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. - * - * Tusky 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 Tusky; if not, - * see . */ - -package com.keylesspalace.tusky - -import com.keylesspalace.tusky.util.MentionTokenizer -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -@RunWith(Parameterized::class) -class MentionTokenizerTest(private val text: CharSequence, - private val expectedStartIndex: Int, - private val expectedEndIndex: Int) { - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun data(): Iterable { - return listOf( - arrayOf("@mention", 1, 8), - arrayOf("@ment10n", 1, 8), - arrayOf("@ment10n_", 1, 9), - arrayOf("@ment10n_n", 1, 10), - arrayOf("@ment10n_9", 1, 10), - arrayOf(" @mention", 2, 9), - arrayOf(" @ment10n", 2, 9), - arrayOf(" @ment10n_", 2, 10), - arrayOf(" @ment10n_ @", 12, 12), - arrayOf(" @ment10n_ @ment20n", 12, 19), - arrayOf(" @ment10n_ @ment20n_", 12, 20), - arrayOf(" @ment10n_ @ment20n_n", 12, 21), - arrayOf(" @ment10n_ @ment20n_9", 12, 21), - arrayOf("mention", 7, 7), - arrayOf("ment10n", 7, 7), - arrayOf("mentio_", 7, 7) - ) - } - } - - private val tokenizer = MentionTokenizer() - - @Test - fun tokenIndices_matchExpectations() { - Assert.assertEquals(expectedStartIndex, tokenizer.findTokenStart(text, text.length)) - Assert.assertEquals(expectedEndIndex, tokenizer.findTokenEnd(text, text.length)) - } -} \ No newline at end of file