allow removing suggestions

This commit is contained in:
Helium314 2023-06-28 20:40:35 +02:00
parent cdabf650c6
commit 601b994941
7 changed files with 209 additions and 36 deletions

View file

@ -396,16 +396,6 @@ public class KeyboardView extends View {
bgX = -padding.left;
bgY = -padding.top;
}
/* if (mUserTheme) {
// color filter is applied to background, which is re-used
// but we don't want it applied to "blue" keys
// so we always need to select the color filter dependent on the current key
if (key.isActionKey()
|| (key.getBackgroundType() == Key.BACKGROUND_TYPE_NORMAL && key.getCode() < 0 && key.getCode() != Constants.CODE_SWITCH_ALPHA_SYMBOL))
background.clearColorFilter();
else
background.setColorFilter(keyBgFilter);
}*/
if (mUserTheme) {
// color filter is applied to background, which is re-used
// but we don't want it applied to "blue" keys

View file

@ -121,6 +121,8 @@ public interface DictionaryFacilitator {
final String dictNamePrefix,
@Nullable final DictionaryInitializationListener listener);
void removeWord(String word);
@UsedForTesting
void resetDictionariesForTesting(
final Context context,

View file

@ -38,8 +38,11 @@ import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils;
import org.dslul.openboard.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -47,6 +50,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -155,6 +160,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
// in this language.
private int mConfidence = 1;
// words cannot be removed from main dictionary, so we use a blacklist instead
public String blacklistFileName = null;
public Set<String> blacklist = new HashSet<>();
// allow to go above max confidence, for better determination of currently preferred language
// when decreasing confidence or getting weight factor, limit to maximum
public void increaseConfidence() {
@ -455,6 +464,20 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
mSecondaryDictionaryGroup == null ? null : secondaryLocale, listener);
}
}
// load blacklists
mDictionaryGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + newLocale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
if (!new File(mDictionaryGroup.blacklistFileName).exists())
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
mDictionaryGroup.blacklist.addAll(readBlacklistFile(mDictionaryGroup.blacklistFileName));
if (mSecondaryDictionaryGroup != null) {
mSecondaryDictionaryGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + secondaryLocale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
if (!new File(mSecondaryDictionaryGroup.blacklistFileName).exists())
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
mSecondaryDictionaryGroup.blacklist.addAll(readBlacklistFile(mSecondaryDictionaryGroup.blacklistFileName));
}
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
}
@ -655,6 +678,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
blockPotentiallyOffensive);
ngramContextForCurrentWord =
ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
// remove entered words from blacklist
if (mDictionaryGroup.blacklist.remove(currentWord))
removeWordFromBlacklistFile(currentWord, mDictionaryGroup.blacklistFileName);
if (mSecondaryDictionaryGroup != null && mSecondaryDictionaryGroup.blacklist.remove(currentWord))
removeWordFromBlacklistFile(currentWord, mSecondaryDictionaryGroup.blacklistFileName);
}
}
@ -834,7 +863,14 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
proximityInfoHandle, settingsValuesForSuggestion, sessionId,
weightForLocale, weightOfLangModelVsSpatialModel);
if (null == dictionarySuggestions) continue;
suggestions.addAll(dictionarySuggestions);
// don't add blacklisted words
// this may not be the most efficient way, but getting suggestions is much slower anyway
for (SuggestedWordInfo info : dictionarySuggestions) {
if (!isBlacklisted(info.getWord())) {
suggestions.add(info);
}
}
}
return suggestions;
}
@ -865,6 +901,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
if (dictionaryGroup.mLocale == null) {
return false;
}
if (isBlacklisted(word)) return false;
for (final String dictType : dictionariesToCheck) {
final Dictionary dictionary = dictionaryGroup.getDict(dictType);
// Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
@ -878,6 +915,87 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
return false;
}
private boolean isBlacklisted(final String word) {
if (mDictionaryGroup.blacklist.contains(word))
return true;
if (mSecondaryDictionaryGroup != null && mSecondaryDictionaryGroup.blacklist.contains(word))
return true;
return false;
}
@Override
public void removeWord(String word) {
removeWordFromGroup(word, mDictionaryGroup);
if (mSecondaryDictionaryGroup != null)
removeWordFromGroup(word, mSecondaryDictionaryGroup);
}
private void removeWordFromGroup(String word, DictionaryGroup group) {
// remove from user history
final ExpandableBinaryDictionary historyDict = group.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (historyDict != null) {
historyDict.removeUnigramEntryDynamically(word);
}
// and from personal dictionary
final ExpandableBinaryDictionary userDict = group.getSubDict(Dictionary.TYPE_USER);
if (userDict != null) {
userDict.removeUnigramEntryDynamically(word);
}
// add to blacklist if in main dictionary
if (group.getDict(Dictionary.TYPE_MAIN).isValidWord(word) && group.blacklist.add(word)) {
// write to file if word wasn't already in blacklist
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
@Override
public void run() {
try {
FileOutputStream fos = new FileOutputStream(group.blacklistFileName, true);
fos.write((word + "\n").getBytes(StandardCharsets.UTF_8));
fos.close();
} catch (IOException e) {
Log.e(TAG, "Exception while trying to write blacklist", e);
}
}
});
}
}
private ArrayList<String> readBlacklistFile(final String filename) {
final ArrayList<String> blacklist = new ArrayList<>();
if (filename == null) return blacklist;
File blacklistFile = new File(filename);
if (!blacklistFile.exists()) return blacklist;
try {
final Scanner scanner = new Scanner(blacklistFile, StandardCharsets.UTF_8.name()).useDelimiter("\n");
while (scanner.hasNext()) {
blacklist.add(scanner.next());
}
} catch (IOException e) {
Log.e(TAG, "Exception while reading blacklist", e);
}
return blacklist;
}
private void removeWordFromBlacklistFile(String word, String filename) {
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
@Override
public void run() {
try {
ArrayList<String> blacklist = readBlacklistFile(filename);
blacklist.remove(word);
FileOutputStream fos = new FileOutputStream(filename);
for (String entry : blacklist) {
fos.write((entry + "\n").getBytes(StandardCharsets.UTF_8));
}
fos.close();
} catch (IOException e) {
Log.e(TAG, "Exception while trying to write blacklist" + filename, e);
}
}
});
}
// called from addWordToUserHistory with a specified dictionary, so provide this dictionary
private int getFrequency(final String word, DictionaryGroup dictGroup) {
if (TextUtils.isEmpty(word)) {

View file

@ -1684,6 +1684,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestedWords(neutralSuggestions);
}
@Override
public void removeSuggestion(final String word) {
mDictionaryFacilitator.removeWord(word);
}
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
void loadKeyboard() {

View file

@ -27,6 +27,7 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
@ -35,6 +36,7 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.RelativeLayout;
import android.widget.TextView;
@ -51,6 +53,7 @@ import org.dslul.openboard.inputmethod.latin.define.DebugFlags;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
import org.dslul.openboard.inputmethod.latin.settings.SettingsValues;
import org.dslul.openboard.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
import org.dslul.openboard.inputmethod.latin.utils.DialogUtils;
import java.util.ArrayList;
@ -62,6 +65,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
void pickSuggestionManually(SuggestedWordInfo word);
void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
void onTextInput(final String rawText);
void removeSuggestion(final String word);
CharSequence getSelection();
}
@ -290,7 +294,57 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
Constants.NOT_A_CODE, this);
return showMoreSuggestions();
if (isShowingMoreSuggestionPanel() || !showMoreSuggestions()) {
for (int i = 0; i < mStartIndexOfMoreSuggestions; i++) {
if (view == mWordViews.get(i)) {
showDeleteSuggestionDialog(mWordViews.get(i));
return true;
}
}
}
return true;
}
private void showDeleteSuggestionDialog(final TextView wordView) {
final String word = wordView.getText().toString();
final PopupMenu menu = new PopupMenu(DialogUtils.getPlatformDialogThemeContext(getContext()), wordView);
menu.getMenu().add(R.string.remove_suggestions);
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
mListener.removeSuggestion(word);
mMoreSuggestionsView.dismissMoreKeysPanel();
// show suggestions, but without the removed word
final ArrayList<SuggestedWordInfo> sw = new ArrayList<SuggestedWordInfo>();
for (int i = 0; i < mSuggestedWords.size(); i ++) {
final SuggestedWordInfo info = mSuggestedWords.getInfo(i);
if (!info.getWord().equals(word))
sw.add(info);
}
ArrayList<SuggestedWordInfo> rs = null;
if (mSuggestedWords.mRawSuggestions != null) {
rs = mSuggestedWords.mRawSuggestions;
for (int i = 0; i < rs.size(); i ++) {
if (rs.get(i).getWord().equals(word)) {
rs.remove(i);
break;
}
}
}
// copied code from setSuggestions, but without the Rtl part
clear();
mSuggestedWords = new SuggestedWords(sw, rs, mSuggestedWords.getTypedWordInfo(),
mSuggestedWords.mTypedWordValid, mSuggestedWords.mWillAutoCorrect,
mSuggestedWords.mIsObsoleteSuggestions, mSuggestedWords.mInputStyle,
mSuggestedWords.mSequenceNumber);
mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions(
getContext(), mSuggestedWords, mSuggestionsStrip, SuggestionStripView.this);
mStripVisibilityGroup.showSuggestionsStrip();
return true;
}
});
menu.show();
}
boolean showMoreSuggestions() {