mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-14 05:52:47 +00:00
Use Openboard's personal dictionary (#322)
Allows more customizablilty than default Android settings More consistent UI (possibly depends on device and settings) --------- Co-authored-by: Helium314 <helium314@mailbox.org>
This commit is contained in:
parent
279c564c72
commit
d223864c81
109 changed files with 1062 additions and 1774 deletions
|
@ -40,7 +40,7 @@ public class RichInputMethodSubtype {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Locale#forLanguageTag is available on API Level 21+.
|
||||
// TODO: Remove this workaround once when we become able to deal with "sr-Latn".
|
||||
map.put(Locale.forLanguageTag("sr-Latn"), new Locale("sr_ZZ"));
|
||||
map.put(Locale.forLanguageTag("sr-Latn"), new Locale("sr", "ZZ"));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
|
|
@ -9,14 +9,10 @@ package org.dslul.openboard.inputmethod.latin.settings;
|
|||
import static org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager.get;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
|
@ -24,18 +20,11 @@ import org.dslul.openboard.inputmethod.latin.R;
|
|||
import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager;
|
||||
import org.dslul.openboard.inputmethod.latin.permissions.PermissionsUtil;
|
||||
import org.dslul.openboard.inputmethod.latin.spellcheck.AndroidSpellCheckerService;
|
||||
import org.dslul.openboard.inputmethod.latin.userdictionary.UserDictionaryList;
|
||||
import org.dslul.openboard.inputmethod.latin.userdictionary.UserDictionarySettings;
|
||||
|
||||
import java.util.TreeSet;
|
||||
|
||||
public final class CorrectionSettingsFragment extends SubScreenFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
PermissionsManager.PermissionsResultCallback {
|
||||
|
||||
private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
|
||||
private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS =
|
||||
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS;
|
||||
private SwitchPreferenceCompat mLookupContactsPreference;
|
||||
|
||||
@Override
|
||||
|
@ -43,16 +32,6 @@ public final class CorrectionSettingsFragment extends SubScreenFragment
|
|||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_correction);
|
||||
|
||||
final PackageManager pm = requireContext().getPackageManager();
|
||||
|
||||
final Preference editPersonalDictionary =
|
||||
findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
|
||||
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
|
||||
final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS ? null
|
||||
: pm.resolveActivity(editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (ri == null) {
|
||||
overwriteUserDictionaryPreference(editPersonalDictionary);
|
||||
}
|
||||
mLookupContactsPreference = findPreference(AndroidSpellCheckerService.PREF_USE_CONTACTS_KEY);
|
||||
|
||||
refreshEnabledSettings();
|
||||
|
@ -100,27 +79,4 @@ public final class CorrectionSettingsFragment extends SubScreenFragment
|
|||
turnOffLookupContactsIfNoPermission();
|
||||
}
|
||||
|
||||
private void overwriteUserDictionaryPreference(final Preference userDictionaryPreference) {
|
||||
final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(requireActivity());
|
||||
if (null == localeList) {
|
||||
// The locale list is null if and only if the user dictionary service is
|
||||
// not present or disabled. In this case we need to remove the preference.
|
||||
getPreferenceScreen().removePreference(userDictionaryPreference);
|
||||
} else if (localeList.size() <= 1) {
|
||||
userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
|
||||
// If the size of localeList is 0, we don't set the locale parameter in the
|
||||
// extras. This will be interpreted by the UserDictionarySettings class as
|
||||
// meaning "the current locale".
|
||||
// Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet()
|
||||
// the locale list always has at least one element, since it always includes the current
|
||||
// locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet().
|
||||
if (localeList.size() == 1) {
|
||||
final String locale = (String)localeList.toArray()[0];
|
||||
userDictionaryPreference.getExtras().putString("locale", locale);
|
||||
}
|
||||
} else {
|
||||
userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,7 +79,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_COLOR_HINT_TEXT_SUFFIX = "hint_text";
|
||||
public static final String PREF_COLOR_BACKGROUND_SUFFIX = "background";
|
||||
public static final String PREF_AUTO_USER_COLOR_SUFFIX = "_auto";
|
||||
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
|
||||
public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
|
||||
public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "pref_key_auto_correction_confidence";
|
||||
public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
|
||||
|
@ -137,7 +136,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_SELECTED_INPUT_STYLE = "pref_selected_input_style";
|
||||
public static final String PREF_USE_SYSTEM_LOCALES = "pref_use_system_locales";
|
||||
public static final String PREF_URL_DETECTION = "pref_url_detection";
|
||||
|
||||
public static final String PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG = "pref_dont_show_missing_dict_dialog";
|
||||
public static final String PREF_PINNED_TOOLBAR_KEYS = "pref_pinned_toolbar_keys";
|
||||
public static final String PREF_TOOLBAR_KEYS = "pref_toolbar_keys";
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.settings;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.provider.UserDictionary;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class UserDictionaryAddWordContents {
|
||||
public static final String EXTRA_MODE = "mode";
|
||||
public static final String EXTRA_WORD = "word";
|
||||
public static final String EXTRA_WEIGHT = "weight";
|
||||
public static final String EXTRA_SHORTCUT = "shortcut";
|
||||
public static final String EXTRA_LOCALE = "locale";
|
||||
|
||||
public static final int MODE_EDIT = 0; // To modify a word
|
||||
public static final int MODE_INSERT = 1; // To add a new or modified word
|
||||
|
||||
static final int CODE_WORD_ADDED = 0;
|
||||
static final int CODE_CANCEL = 1;
|
||||
static final int CODE_UPDATED = 2;
|
||||
static final int CODE_ALREADY_PRESENT = 3;
|
||||
|
||||
public static final int WEIGHT_FOR_USER_DICTIONARY_ADDS = 250;
|
||||
|
||||
private int mMode; // Either MODE_EDIT or MODE_INSERT
|
||||
private final EditText mWordEditText;
|
||||
private final EditText mShortcutEditText;
|
||||
private final EditText mWeightEditText;
|
||||
private String mLocaleString;
|
||||
private final String mOldWord;
|
||||
private final String mOldShortcut;
|
||||
private final String mOldWeight;
|
||||
private String mSavedWord;
|
||||
private String mSavedShortcut;
|
||||
private String mSavedWeight;
|
||||
private Context mContext;
|
||||
|
||||
UserDictionaryAddWordContents(final View view, final Bundle args) {
|
||||
mWordEditText = view.findViewById(R.id.user_dictionary_add_word_text);
|
||||
mWordEditText.requestFocus();
|
||||
mShortcutEditText = view.findViewById(R.id.user_dictionary_add_shortcut);
|
||||
mWeightEditText = view.findViewById(R.id.user_dictionary_add_weight);
|
||||
mWeightEditText.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
|
||||
final Button deleteWordButton = view.findViewById(R.id.user_dictionary_delete_button);
|
||||
|
||||
final String word = args.getString(EXTRA_WORD);
|
||||
if (null != word) {
|
||||
mWordEditText.setText(word);
|
||||
// Use getText in case the edit text modified the text we set. This happens when
|
||||
// it's too long to be edited.
|
||||
mWordEditText.setSelection(mWordEditText.getText().length());
|
||||
}
|
||||
|
||||
final String shortcut = args.getString(EXTRA_SHORTCUT);
|
||||
if (null != shortcut) {
|
||||
mShortcutEditText.setText(shortcut);
|
||||
}
|
||||
mOldShortcut = args.getString(EXTRA_SHORTCUT);
|
||||
|
||||
final String weight = args.getString(EXTRA_WEIGHT);
|
||||
if (null != weight) {
|
||||
mWeightEditText.setText(weight);
|
||||
}
|
||||
|
||||
mMode = args.getInt(EXTRA_MODE);
|
||||
if (mMode == MODE_EDIT) {
|
||||
deleteWordButton.setVisibility(View.VISIBLE);
|
||||
} else if (mMode == MODE_INSERT) {
|
||||
deleteWordButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
mOldWord = args.getString(EXTRA_WORD);
|
||||
mOldWeight = args.getString(EXTRA_WEIGHT);
|
||||
updateLocale(mContext, args.getString(EXTRA_LOCALE));
|
||||
}
|
||||
|
||||
UserDictionaryAddWordContents(final View view, final UserDictionaryAddWordContents oldInstanceToBeEdited) {
|
||||
mWordEditText = view.findViewById(R.id.user_dictionary_add_word_text);
|
||||
mShortcutEditText = view.findViewById(R.id.user_dictionary_add_shortcut);
|
||||
mWeightEditText = view.findViewById(R.id.user_dictionary_add_weight);
|
||||
|
||||
mOldWord = oldInstanceToBeEdited.mSavedWord;
|
||||
mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
|
||||
mOldWeight = oldInstanceToBeEdited.mSavedWeight;
|
||||
updateLocale(mContext, mLocaleString);
|
||||
}
|
||||
|
||||
// locale may be null, this means system locale
|
||||
// It may also be the empty string, which means "For all languages"
|
||||
void updateLocale(final Context context, final String locale) {
|
||||
mContext = context;
|
||||
|
||||
mLocaleString = null == locale
|
||||
? mContext.getResources().getConfiguration().locale.toString()
|
||||
: locale;
|
||||
// The keyboard uses the language layout of the user dictionary
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
mWordEditText.setImeHintLocales(new LocaleList(LocaleUtils.constructLocaleFromString(mLocaleString)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String getLocale() {
|
||||
return mLocaleString;
|
||||
}
|
||||
|
||||
void delete(final Context context) {
|
||||
// do nothing if mode is wrong or word is empty
|
||||
if (MODE_EDIT != mMode || TextUtils.isEmpty(mOldWord))
|
||||
return;
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
// Remove the old entry.
|
||||
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, mLocaleString, resolver);
|
||||
}
|
||||
|
||||
public final int apply(@NonNull final Context context) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final String newWord = mWordEditText.getText().toString();
|
||||
final String locale = mLocaleString;
|
||||
|
||||
if (TextUtils.isEmpty(newWord)) {
|
||||
// If the word is empty, don't insert it.
|
||||
return CODE_CANCEL;
|
||||
}
|
||||
|
||||
mSavedShortcut = mShortcutEditText.getText().toString();
|
||||
if (TextUtils.isEmpty(mSavedShortcut)) {
|
||||
mSavedShortcut = null;
|
||||
}
|
||||
|
||||
mSavedWeight = mWeightEditText.getText().toString();
|
||||
if (TextUtils.isEmpty(mSavedWeight)) {
|
||||
mSavedWeight = String.valueOf(WEIGHT_FOR_USER_DICTIONARY_ADDS);
|
||||
}
|
||||
|
||||
mSavedWord = newWord;
|
||||
|
||||
// In edit mode, everything is modified without overwriting other existing words
|
||||
if (MODE_EDIT == mMode && hasWord(newWord, locale, context) && newWord.equals(mOldWord)) {
|
||||
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, locale, resolver);
|
||||
} else {
|
||||
mMode = MODE_INSERT;
|
||||
}
|
||||
|
||||
if (mMode == MODE_INSERT && hasWord(newWord, locale, context)) {
|
||||
return CODE_ALREADY_PRESENT;
|
||||
}
|
||||
|
||||
if (mMode == MODE_INSERT) {
|
||||
// Delete duplicate when adding or updating new word
|
||||
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, locale, resolver);
|
||||
// Update the existing word by adding a new one
|
||||
UserDictionary.Words.addWord(context, newWord,
|
||||
Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ?
|
||||
null : LocaleUtils.constructLocaleFromString(mLocaleString));
|
||||
|
||||
return CODE_UPDATED;
|
||||
}
|
||||
|
||||
// Delete duplicates
|
||||
UserDictionarySettings.deleteWord(newWord, locale, resolver);
|
||||
|
||||
// In this class we use the empty string to represent 'all locales' and mLocale cannot
|
||||
// be null. However the addWord method takes null to mean 'all locales'.
|
||||
UserDictionary.Words.addWord(context, newWord,
|
||||
Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ?
|
||||
null : LocaleUtils.constructLocaleFromString(mLocaleString));
|
||||
|
||||
return CODE_WORD_ADDED;
|
||||
}
|
||||
|
||||
public boolean isExistingWord(final Context context) {
|
||||
final String newWord = mWordEditText.getText().toString();
|
||||
if (mMode != MODE_EDIT) {
|
||||
return hasWord(newWord, mLocaleString, context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
|
||||
private static final String HAS_WORD_AND_LOCALE_SELECTION = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=?";
|
||||
private static final String HAS_WORD_AND_ALL_LOCALES_SELECTION = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + " is null";
|
||||
|
||||
private boolean hasWord(final String word, final String locale, final Context context) {
|
||||
final Cursor cursor;
|
||||
|
||||
if ("".equals(locale)) {
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_AND_ALL_LOCALES_SELECTION,
|
||||
new String[] { word }, null);
|
||||
} else {
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_AND_LOCALE_SELECTION,
|
||||
new String[] { word, locale}, null);
|
||||
}
|
||||
try {
|
||||
if (null == cursor) return false;
|
||||
return cursor.getCount() > 0;
|
||||
} finally {
|
||||
if (null != cursor) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocaleRenderer {
|
||||
private final String mLocaleString;
|
||||
private final String mDescription;
|
||||
|
||||
public LocaleRenderer(final Context context, @Nullable final String localeString) {
|
||||
mLocaleString = localeString;
|
||||
|
||||
if (null == localeString || "".equals(localeString)) {
|
||||
mDescription = context.getString(R.string.user_dict_settings_all_languages);
|
||||
} else {
|
||||
mDescription = UserDictionarySettings.getLocaleDisplayName(context, localeString);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDescription;
|
||||
}
|
||||
public String getLocaleString() {
|
||||
return mLocaleString;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void addLocaleDisplayNameToList(final Context context,
|
||||
final ArrayList<LocaleRenderer> list, final String locale) {
|
||||
if (null != locale) {
|
||||
list.add(new LocaleRenderer(context, locale));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get the list of locales and subtypes to display for this word
|
||||
public ArrayList<LocaleRenderer> getLocaleRendererList(final Context context) {
|
||||
final TreeSet<String> sortedLanguages = UserDictionaryListFragment.getSortedDictionaryLocaleStrings(context);
|
||||
|
||||
// mLocale is removed from the language list as it will be added to the top of the list
|
||||
sortedLanguages.remove(mLocaleString);
|
||||
// "For all languages" is removed from the language list as it will be added at the end of the list
|
||||
sortedLanguages.remove("");
|
||||
|
||||
// final list of locales to show
|
||||
final ArrayList<LocaleRenderer> localesList = new ArrayList<>();
|
||||
// First, add the language of the personal dictionary at the top of the list
|
||||
addLocaleDisplayNameToList(context, localesList, mLocaleString);
|
||||
|
||||
// Next, add all other languages which will be sorted alphabetically in UserDictionaryAddWordFragment.updateSpinner()
|
||||
for (String language : sortedLanguages) {
|
||||
addLocaleDisplayNameToList(context, localesList, language);
|
||||
}
|
||||
|
||||
// Finally, add "All languages" at the end of the list
|
||||
if (!"".equals(mLocaleString)) {
|
||||
addLocaleDisplayNameToList(context, localesList, "");
|
||||
}
|
||||
|
||||
return localesList;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.TextViewKt;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.UserDictionaryAddWordContents.LocaleRenderer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Fragment to add a word/shortcut to the user dictionary.
|
||||
* As opposed to the UserDictionaryActivity, this is only invoked within Settings
|
||||
* from the UserDictionarySettings.
|
||||
*/
|
||||
public class UserDictionaryAddWordFragment extends SubScreenFragment {
|
||||
|
||||
private UserDictionaryAddWordContents mContents;
|
||||
private View mRootView;
|
||||
private EditText mWordEditText;
|
||||
private EditText mWeightEditText;
|
||||
private InputMethodManager mInput;
|
||||
private ActionBar mActionBar;
|
||||
private String mLocaleDisplayString;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedState) {
|
||||
mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
|
||||
// If we have a non-null mContents object, it's the old value before a configuration
|
||||
// change (eg rotation) so we need to use its values. Otherwise, read from the arguments.
|
||||
if (null == mContents) {
|
||||
mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
|
||||
} else {
|
||||
// We create a new mContents object to account for the new situation : a word has
|
||||
// been added to the user dictionary when we started rotating, and we are now editing
|
||||
// it. That means in particular if the word undergoes any change, the old version should
|
||||
// be updated, so the mContents object needs to switch to EDIT mode if it was in
|
||||
// INSERT mode.
|
||||
mContents = new UserDictionaryAddWordContents(mRootView, mContents);
|
||||
}
|
||||
|
||||
mWordEditText = mRootView.findViewById(R.id.user_dictionary_add_word_text);
|
||||
mWeightEditText = mRootView.findViewById(R.id.user_dictionary_add_weight);
|
||||
|
||||
final Bundle args = getArguments();
|
||||
mActionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
mLocaleDisplayString = UserDictionarySettings.getLocaleDisplayName(requireContext(), mContents.getLocale());
|
||||
if (args != null && mActionBar != null) {
|
||||
if (args.getInt(UserDictionaryAddWordContents.EXTRA_MODE) == UserDictionaryAddWordContents.MODE_EDIT) {
|
||||
mActionBar.setTitle(R.string.user_dict_settings_edit_dialog_title);
|
||||
} else {
|
||||
mActionBar.setTitle(R.string.user_dict_settings_add_dialog_title);
|
||||
}
|
||||
mActionBar.setSubtitle(mLocaleDisplayString);
|
||||
}
|
||||
|
||||
final Button saveWordButton = mRootView.findViewById(R.id.user_dictionary_save_button);
|
||||
saveWordButton.setOnClickListener(v -> addWord());
|
||||
|
||||
TextViewKt.doAfterTextChanged(mWordEditText, (editable) -> {
|
||||
final int visibility = TextUtils.isEmpty(editable.toString()) ? View.INVISIBLE : View.VISIBLE;
|
||||
saveWordButton.setVisibility(visibility);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
saveWordButton.setVisibility(TextUtils.isEmpty(mWordEditText.getText().toString()) ? View.INVISIBLE : View.VISIBLE);
|
||||
|
||||
final Button deleteWordButton = mRootView.findViewById(R.id.user_dictionary_delete_button);
|
||||
final Drawable deleteWordIcon = toScaledBitmapDrawable(R.drawable.ic_delete, 0.75f);
|
||||
deleteWordButton.setCompoundDrawablesWithIntrinsicBounds(null, null, deleteWordIcon, null);
|
||||
deleteWordButton.setOnClickListener(v -> {
|
||||
mContents.delete(requireContext());
|
||||
requireActivity().onBackPressed();
|
||||
});
|
||||
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// Automatically display the keyboard when we want to add or modify a word
|
||||
mInput = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
mInput.showSoftInput(mWordEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
// Add a word using the Enter key
|
||||
mWeightEditText.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
addWord();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// We are being shown: display the word
|
||||
updateSpinner();
|
||||
}
|
||||
|
||||
// The bin icon is too big compared to the plus icon; we need to reduce it.
|
||||
// We therefore need to convert the drawable image to a BitmapDrawable.
|
||||
private BitmapDrawable toScaledBitmapDrawable(int drawableResId, float scale) {
|
||||
final Drawable drawable = ContextCompat.getDrawable(requireContext(), drawableResId);
|
||||
if (drawable == null) return null;
|
||||
final int height = (int) (scale * drawable.getIntrinsicHeight());
|
||||
final int width = (int) (scale * drawable.getIntrinsicWidth());
|
||||
drawable.setBounds(0, 0, width, height);
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
drawable.draw(canvas);
|
||||
return new BitmapDrawable(getResources(), bitmap);
|
||||
}
|
||||
|
||||
private void addWord() {
|
||||
if (mContents.isExistingWord(requireContext())) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(getString(R.string.user_dict_word_already_present, mLocaleDisplayString))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
mInput.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY))
|
||||
.show();
|
||||
|
||||
mWordEditText.requestFocus();
|
||||
|
||||
} else if (!mWordEditText.getText().toString().isEmpty()) {
|
||||
mContents.apply(requireContext());
|
||||
requireActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSpinner() {
|
||||
final ArrayList<LocaleRenderer> localesList = mContents.getLocaleRendererList(requireContext());
|
||||
|
||||
final Spinner localeSpinner = mRootView.findViewById(R.id.user_dictionary_add_locale);
|
||||
final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
|
||||
requireContext(), android.R.layout.simple_spinner_item, localesList);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
localeSpinner.setAdapter(adapter);
|
||||
localeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(position);
|
||||
|
||||
mContents.updateLocale(requireContext(), locale.getLocaleString());
|
||||
// To have the selected language at the top of the list, this one is removed from the list
|
||||
localesList.remove(position);
|
||||
// The other languages are then sorted alphabetically by name, with the exception of "For all languages"
|
||||
Collections.sort(localesList, (locale1, locale2) -> {
|
||||
if (!locale1.getLocaleString().equals("") && !locale2.getLocaleString().equals("")) {
|
||||
return locale1.toString().compareToIgnoreCase(locale2.toString());
|
||||
} else {
|
||||
return locale1.getLocaleString().compareToIgnoreCase(locale2.getLocaleString());
|
||||
}
|
||||
});
|
||||
|
||||
// Set "For all languages" to the end of the list
|
||||
if (!locale.getLocaleString().equals("")) {
|
||||
// After alphabetical sorting, "For all languages" is always in 1st position.
|
||||
// (The position is 0 because the spinner menu item count starts at 0)
|
||||
final LocaleRenderer forAllLanguages = adapter.getItem(0);
|
||||
// So we delete its entry ...
|
||||
localesList.remove(forAllLanguages);
|
||||
// ... and we set it at the end of the list.
|
||||
localesList.add(localesList.size(), forAllLanguages);
|
||||
}
|
||||
|
||||
// Finally, we add the selected language to the top of the list.
|
||||
localesList.add(0, locale);
|
||||
|
||||
// When a language is selected, the keyboard layout changes automatically
|
||||
mInput.restartInput(mWordEditText);
|
||||
|
||||
// The action bar subtitle is updated when a language is selected in the drop-down menu
|
||||
mActionBar.setSubtitle(locale.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
// I'm not sure we can come here, but if we do, that's the right thing to do.
|
||||
final Bundle args = getArguments();
|
||||
if (args == null) return;
|
||||
mContents.updateLocale(requireContext(), args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class UserDictionaryListFragment extends SubScreenFragment {
|
||||
|
||||
// TODO : Implement the import/export function in these menus
|
||||
/*private static final int OPTIONS_MENU_EXPORT = Menu.NONE;
|
||||
private static final int OPTIONS_MENU_IMPORT = Menu.NONE;*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
|
||||
|
||||
createUserDictSettings(getPreferenceScreen());
|
||||
// TODO : Uncomment to create the import/export function in the menu
|
||||
//setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(R.string.edit_personal_dictionary);
|
||||
actionBar.setSubtitle(null);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
LinearLayout view = (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
|
||||
View v = inflater.inflate(R.layout.user_dictionary_settings_list_fragment, null);
|
||||
Button addWordButton = v.findViewById(R.id.user_dictionary_add_word_button);
|
||||
addWordButton.setOnClickListener(v1 -> showAddWordFragment());
|
||||
view.addView(v);
|
||||
return view;
|
||||
}
|
||||
|
||||
// TODO : Implement the import/export function in these menus
|
||||
/* @Override
|
||||
public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) {
|
||||
*//*menu.add(0, OPTIONS_MENU_EXPORT, 0, R.string.button_backup);
|
||||
menu.add(0, OPTIONS_MENU_IMPORT, 0, R.string.button_restore);*//*
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == OPTIONS_MENU_EXPORT) {
|
||||
return true;
|
||||
}
|
||||
if (item.getItemId() == OPTIONS_MENU_IMPORT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Creates the entries that allow the user to go into the user dictionary for each locale.
|
||||
* @param userDictGroup The group to put the settings in.
|
||||
*/
|
||||
private void createUserDictSettings(final PreferenceGroup userDictGroup) {
|
||||
final TreeSet<String> sortedLanguages = getSortedDictionaryLocaleStrings(requireContext());
|
||||
|
||||
// Add preference "for all locales"
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(""));
|
||||
// Add preference for each dictionary locale
|
||||
for (String localeUserDictionary : sortedLanguages) {
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(localeUserDictionary));
|
||||
}
|
||||
}
|
||||
|
||||
static TreeSet<String> getSortedDictionaryLocaleStrings(final Context context) {
|
||||
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(context);
|
||||
final boolean localeSystemOnly = prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true);
|
||||
final TreeSet<String> sortedLanguages = new TreeSet<>(String::compareToIgnoreCase);
|
||||
|
||||
// Add the main language selected in the "Language and Layouts" setting except "No language"
|
||||
for (InputMethodSubtype mainSubtype : SubtypeSettingsKt.getEnabledSubtypes(prefs, true)) {
|
||||
if (!mainSubtype.getLocale().equals("zz")) {
|
||||
sortedLanguages.add(mainSubtype.getLocale());
|
||||
}
|
||||
// Secondary language is added only if main language is selected and if system language is not enabled
|
||||
if (!localeSystemOnly) {
|
||||
for (Locale secondaryLocale : Settings.getSecondaryLocales(prefs, mainSubtype.getLocale())) {
|
||||
sortedLanguages.add(secondaryLocale.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Locale systemSubtype : SubtypeSettingsKt.getSystemLocales()) {
|
||||
sortedLanguages.add(systemSubtype.toString());
|
||||
}
|
||||
return sortedLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single User Dictionary Preference object, with its parameters set.
|
||||
* @param localeString The locale for which this user dictionary is for.
|
||||
* @return The corresponding preference.
|
||||
*/
|
||||
private Preference createUserDictionaryPreference(@NonNull final String localeString) {
|
||||
final Preference newPref = new Preference(requireContext());
|
||||
|
||||
if (localeString.isEmpty()) {
|
||||
newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
|
||||
} else {
|
||||
newPref.setTitle(UserDictionarySettings.getLocaleDisplayName(requireContext(), localeString));
|
||||
}
|
||||
newPref.getExtras().putString("locale", localeString);
|
||||
newPref.setIconSpaceReserved(false);
|
||||
newPref.setFragment(UserDictionarySettings.class.getName());
|
||||
|
||||
return newPref;
|
||||
}
|
||||
|
||||
private void showAddWordFragment() {
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, UserDictionaryAddWordContents.MODE_INSERT);
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_WORD, "");
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, "");
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, ""); // Empty means "For all languages"
|
||||
AppCompatActivity activity = (AppCompatActivity) requireActivity();
|
||||
activity.getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, UserDictionaryAddWordFragment.class, args)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
package org.dslul.openboard.inputmethod.latin.settings;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
@ -14,12 +14,11 @@ import android.os.Bundle;
|
|||
import android.provider.UserDictionary;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AlphabetIndexer;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SectionIndexer;
|
||||
|
@ -33,64 +32,65 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
import androidx.fragment.app.ListFragment;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
// Caveat: This class is basically taken from
|
||||
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionarySettings.java
|
||||
// in order to deal with some devices that have issues with the user dictionary handling
|
||||
|
||||
public class UserDictionarySettings extends ListFragment {
|
||||
|
||||
public static final boolean IS_SHORTCUT_API_SUPPORTED = true;
|
||||
private static final String[] QUERY_PROJECTION =
|
||||
{ UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT, UserDictionary.Words.FREQUENCY };
|
||||
|
||||
private static final String[] QUERY_PROJECTION_SHORTCUT_UNSUPPORTED =
|
||||
{ UserDictionary.Words._ID, UserDictionary.Words.WORD};
|
||||
private static final String[] QUERY_PROJECTION_SHORTCUT_SUPPORTED =
|
||||
{ UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT};
|
||||
private static final String[] QUERY_PROJECTION = IS_SHORTCUT_API_SUPPORTED
|
||||
? QUERY_PROJECTION_SHORTCUT_SUPPORTED
|
||||
: QUERY_PROJECTION_SHORTCUT_UNSUPPORTED;
|
||||
|
||||
// The index of the shortcut in the above array.
|
||||
// The index of the shortcut in the above array (1 is used for the words)
|
||||
private static final int INDEX_SHORTCUT = 2;
|
||||
private static final int INDEX_WEIGHT = 3;
|
||||
|
||||
private static final String[] ADAPTER_FROM_SHORTCUT_UNSUPPORTED = {
|
||||
UserDictionary.Words.WORD,
|
||||
private static final String[] ADAPTER_FROM = {
|
||||
UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT, UserDictionary.Words.FREQUENCY
|
||||
};
|
||||
|
||||
private static final String[] ADAPTER_FROM_SHORTCUT_SUPPORTED = {
|
||||
UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
|
||||
private static final int[] ADAPTER_TO = {
|
||||
R.id.user_dictionary_item_word, R.id.user_dictionary_item_shortcut
|
||||
};
|
||||
|
||||
private static final String[] ADAPTER_FROM = IS_SHORTCUT_API_SUPPORTED ?
|
||||
ADAPTER_FROM_SHORTCUT_SUPPORTED : ADAPTER_FROM_SHORTCUT_UNSUPPORTED;
|
||||
|
||||
private static final int[] ADAPTER_TO_SHORTCUT_UNSUPPORTED = {
|
||||
android.R.id.text1,
|
||||
};
|
||||
|
||||
private static final int[] ADAPTER_TO_SHORTCUT_SUPPORTED = {
|
||||
android.R.id.text1, android.R.id.text2
|
||||
};
|
||||
|
||||
private static final int[] ADAPTER_TO = IS_SHORTCUT_API_SUPPORTED ?
|
||||
ADAPTER_TO_SHORTCUT_SUPPORTED : ADAPTER_TO_SHORTCUT_UNSUPPORTED;
|
||||
|
||||
// Either the locale is empty (means the word is applicable to all locales)
|
||||
// or the word equals our current locale
|
||||
private static final String QUERY_SELECTION = UserDictionary.Words.LOCALE + "=?";
|
||||
private static final String QUERY_SELECTION_ALL_LOCALES = UserDictionary.Words.LOCALE + " is null";
|
||||
|
||||
private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD
|
||||
+ "=? AND " + UserDictionary.Words.SHORTCUT + "=?";
|
||||
private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD
|
||||
+ "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR "
|
||||
+ UserDictionary.Words.SHORTCUT + "=''";
|
||||
private static final String DELETE_SELECTION_SHORTCUT_UNSUPPORTED =
|
||||
UserDictionary.Words.WORD + "=?";
|
||||
private static final String DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_LOCALE = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.SHORTCUT + "=? AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=?";
|
||||
|
||||
private static final int OPTIONS_MENU_ADD = Menu.FIRST;
|
||||
private static final String DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.SHORTCUT + "=? AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + " is null";
|
||||
|
||||
private static final String DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.SHORTCUT + " is null AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=? OR "
|
||||
|
||||
+ UserDictionary.Words.SHORTCUT + "='' AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=?";
|
||||
|
||||
private static final String DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.SHORTCUT + " is null AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + " is null OR "
|
||||
|
||||
+ UserDictionary.Words.SHORTCUT + "='' AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + " is null";
|
||||
|
||||
private static final String DELETE_WORD_AND_LOCALE = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=?";
|
||||
|
||||
private static final String DELETE_WORD_AND_ALL_LOCALES = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + " is null";
|
||||
|
||||
private Cursor mCursor;
|
||||
|
||||
|
@ -106,7 +106,10 @@ public class UserDictionarySettings extends ListFragment {
|
|||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.user_dictionary_preference_list_fragment, container, false);
|
||||
LinearLayout view = (LinearLayout) inflater.inflate(R.layout.user_dictionary_settings_list_fragment, container, false);
|
||||
Button addWordButton = view.findViewById(R.id.user_dictionary_add_word_button);
|
||||
addWordButton.setOnClickListener(v -> showAddOrEditFragment(null, null, null));
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -125,12 +128,7 @@ public class UserDictionarySettings extends ListFragment {
|
|||
} else locale = localeFromIntent;
|
||||
|
||||
mLocale = locale;
|
||||
// WARNING: The following cursor is never closed! TODO: don't put that in a member, and
|
||||
// make sure all cursors are correctly closed. Also, this comes from a call to
|
||||
// Activity#managedQuery, which has been deprecated for a long time (and which FORBIDS
|
||||
// closing the cursor, so take care when resolving this TODO). We should either use a
|
||||
// regular query and close the cursor, or switch to a LoaderManager and a CursorLoader.
|
||||
mCursor = createCursor(locale);
|
||||
createCursor(locale);
|
||||
TextView emptyView = view.findViewById(android.R.id.empty);
|
||||
emptyView.setText(R.string.user_dict_settings_empty_text);
|
||||
|
||||
|
@ -139,30 +137,18 @@ public class UserDictionarySettings extends ListFragment {
|
|||
listView.setFastScrollEnabled(true);
|
||||
listView.setEmptyView(emptyView);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
// Show the language as a subtitle of the action bar
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
// clear the subtitle
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setSubtitle(null);
|
||||
actionBar.setTitle(R.string.edit_personal_dictionary);
|
||||
actionBar.setSubtitle(getLocaleDisplayName(requireContext(), mLocale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
|
||||
ListAdapter adapter = getListView().getAdapter();
|
||||
if (adapter instanceof MyAdapter listAdapter) {
|
||||
// The list view is forced refreshed here. This allows the changes done
|
||||
|
@ -172,67 +158,56 @@ public class UserDictionarySettings extends ListFragment {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Cursor createCursor(final String locale) {
|
||||
private void createCursor(final String locale) {
|
||||
// Locale can be any of:
|
||||
// - The string representation of a locale, as returned by Locale#toString()
|
||||
// - The empty string. This means we want a cursor returning words valid for all locales.
|
||||
// - null. This means we want a cursor for the current locale, whatever this is.
|
||||
|
||||
// Note that this contrasts with the data inside the database, where NULL means "all
|
||||
// locales" and there should never be an empty string. The confusion is called by the
|
||||
// historical use of null for "all locales".
|
||||
// locales" and there should never be an empty string.
|
||||
// The confusion is called by the historical use of null for "all locales".
|
||||
|
||||
// TODO: it should be easy to make this more readable by making the special values
|
||||
// human-readable, like "all_locales" and "current_locales" strings, provided they
|
||||
// can be guaranteed not to match locales that may exist.
|
||||
// human-readable, like "all_locales" and "current_locales" strings, provided they
|
||||
// can be guaranteed not to match locales that may exist.
|
||||
|
||||
if ("".equals(locale)) {
|
||||
// Case-insensitive sort
|
||||
return requireActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
QUERY_SELECTION_ALL_LOCALES, null,
|
||||
"UPPER(" + UserDictionary.Words.WORD + ")");
|
||||
} else {
|
||||
final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
|
||||
mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
QUERY_SELECTION, new String[] { queryLocale },
|
||||
"UPPER(" + UserDictionary.Words.WORD + ")");
|
||||
}
|
||||
final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
|
||||
return requireActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
QUERY_SELECTION, new String[] { queryLocale },
|
||||
"UPPER(" + UserDictionary.Words.WORD + ")");
|
||||
}
|
||||
|
||||
private ListAdapter createAdapter() {
|
||||
return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor,
|
||||
return new MyAdapter(requireContext(), R.layout.user_dictionary_item, mCursor,
|
||||
ADAPTER_FROM, ADAPTER_TO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
|
||||
final String word = getWord(position);
|
||||
final String shortcut = getShortcut(position);
|
||||
final String weight = getWeight(position);
|
||||
if (word != null) {
|
||||
showAddOrEditDialog(word, shortcut);
|
||||
showAddOrEditFragment(word, shortcut, weight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
|
||||
final Locale systemLocale = getResources().getConfiguration().locale;
|
||||
if (!TextUtils.isEmpty(mLocale) && !mLocale.equals(systemLocale.toString())) {
|
||||
// Hide the add button for ICS because it doesn't support specifying a locale
|
||||
// for an entry. This new "locale"-aware API has been added in conjunction
|
||||
// with the shortcut API.
|
||||
return;
|
||||
}
|
||||
public static String getLocaleDisplayName(Context context, String localeStr) {
|
||||
if (TextUtils.isEmpty(localeStr)) {
|
||||
// CAVEAT: localeStr should not be null because a null locale stands for the system
|
||||
// locale in UserDictionary.Words.addWord.
|
||||
return context.getResources().getString(R.string.user_dict_settings_all_languages);
|
||||
}
|
||||
MenuItem actionItem = menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
|
||||
.setIcon(R.drawable.ic_plus);
|
||||
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == OPTIONS_MENU_ADD) {
|
||||
showAddOrEditDialog(null, null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
final Locale locale = LocaleUtils.constructLocaleFromString(localeStr);
|
||||
return LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,13 +215,14 @@ public class UserDictionarySettings extends ListFragment {
|
|||
* @param editingWord the word to edit, or null if it's an add.
|
||||
* @param editingShortcut the shortcut for this entry, or null if none.
|
||||
*/
|
||||
private void showAddOrEditDialog(final String editingWord, final String editingShortcut) {
|
||||
private void showAddOrEditFragment(final String editingWord, final String editingShortcut, final String editingWeight) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord
|
||||
? UserDictionaryAddWordContents.MODE_INSERT
|
||||
: UserDictionaryAddWordContents.MODE_EDIT);
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_WEIGHT, editingWeight);
|
||||
args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale);
|
||||
AppCompatActivity activity = (AppCompatActivity) requireActivity();
|
||||
activity.getSupportFragmentManager().beginTransaction()
|
||||
|
@ -256,57 +232,82 @@ public class UserDictionarySettings extends ListFragment {
|
|||
}
|
||||
|
||||
private String getWord(final int position) {
|
||||
if (null == mCursor) return null;
|
||||
mCursor.moveToPosition(position);
|
||||
// Handle a possible race-condition
|
||||
if (mCursor.isAfterLast()) return null;
|
||||
|
||||
return mCursor.getString(
|
||||
mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
|
||||
return getEntry(position, UserDictionary.Words.WORD);
|
||||
}
|
||||
|
||||
private String getShortcut(final int position) {
|
||||
if (!IS_SHORTCUT_API_SUPPORTED) return null;
|
||||
return getEntry(position, UserDictionary.Words.SHORTCUT);
|
||||
}
|
||||
|
||||
private String getWeight(final int position) {
|
||||
return getEntry(position, UserDictionary.Words.FREQUENCY);
|
||||
}
|
||||
|
||||
private String getEntry(final int position, final String column) {
|
||||
if (null == mCursor) return null;
|
||||
mCursor.moveToPosition(position);
|
||||
// Handle a possible race-condition
|
||||
if (mCursor.isAfterLast()) return null;
|
||||
|
||||
return mCursor.getString(
|
||||
mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT));
|
||||
return mCursor.getString(mCursor.getColumnIndexOrThrow(column));
|
||||
}
|
||||
|
||||
public static void deleteWord(final String word, final String shortcut,
|
||||
final ContentResolver resolver) {
|
||||
if (!IS_SHORTCUT_API_SUPPORTED) {
|
||||
resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_SHORTCUT_UNSUPPORTED,
|
||||
new String[] { word });
|
||||
} else if (TextUtils.isEmpty(shortcut)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT,
|
||||
new String[] { word });
|
||||
public static void deleteWordInEditMode(final String word, final String shortcut, final String weight,
|
||||
final String locale, final ContentResolver resolver) {
|
||||
if (TextUtils.isEmpty(shortcut)) {
|
||||
if ("".equals(locale)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES,
|
||||
new String[] { word, weight });
|
||||
} else {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE,
|
||||
new String[] { word, weight, locale });
|
||||
}
|
||||
} else {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT,
|
||||
new String[] { word, shortcut });
|
||||
if ("".equals(locale)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES,
|
||||
new String[] { word, shortcut, weight });
|
||||
} else {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_LOCALE,
|
||||
new String[] { word, shortcut, weight, locale });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
|
||||
public static void deleteWord(final String word, final String locale, final ContentResolver resolver) {
|
||||
if ("".equals(locale)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_WORD_AND_ALL_LOCALES,
|
||||
new String[] { word });
|
||||
} else {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_WORD_AND_LOCALE,
|
||||
new String[] { word, locale });
|
||||
}
|
||||
}
|
||||
|
||||
private class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
|
||||
private AlphabetIndexer mIndexer;
|
||||
|
||||
private final ViewBinder mViewBinder = (v, c, columnIndex) -> {
|
||||
if (!IS_SHORTCUT_API_SUPPORTED) {
|
||||
// just let SimpleCursorAdapter set the view values
|
||||
return false;
|
||||
}
|
||||
final String weightTitle = String.format(getString(R.string.user_dict_settings_add_weight_value));
|
||||
final String weightText = c.getString(INDEX_WEIGHT);
|
||||
final String weight = weightTitle + " " + weightText;
|
||||
|
||||
final String shortcutTitle = String.format(getString(R.string.user_dict_settings_add_shortcut_option_name));
|
||||
final String shortcutText = c.getString(INDEX_SHORTCUT);
|
||||
final String shortcut = shortcutTitle + " " + shortcutText;
|
||||
|
||||
final String weightAndShortcut = weight + " | " + shortcut;
|
||||
|
||||
if (columnIndex == INDEX_SHORTCUT) {
|
||||
final String shortcut = c.getString(INDEX_SHORTCUT);
|
||||
if (TextUtils.isEmpty(shortcut)) {
|
||||
v.setVisibility(View.GONE);
|
||||
if (TextUtils.isEmpty(shortcutText)) {
|
||||
((TextView)v).setText(weight);
|
||||
} else {
|
||||
((TextView)v).setText(shortcut);
|
||||
v.setVisibility(View.VISIBLE);
|
||||
((TextView)v).setText(weightAndShortcut);
|
||||
}
|
||||
v.invalidate();
|
||||
return true;
|
||||
|
@ -342,5 +343,4 @@ public class UserDictionarySettings extends ListFragment {
|
|||
return null == mIndexer ? null : mIndexer.getSections();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.UserDictionary;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
// Caveat: This class is basically taken from
|
||||
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
|
||||
// in order to deal with some devices that have issues with the user dictionary handling
|
||||
|
||||
/**
|
||||
* A container class to factor common code to UserDictionaryAddWordFragment
|
||||
* and UserDictionaryAddWordActivity.
|
||||
*/
|
||||
public class UserDictionaryAddWordContents {
|
||||
public static final String EXTRA_MODE = "mode";
|
||||
public static final String EXTRA_WORD = "word";
|
||||
public static final String EXTRA_SHORTCUT = "shortcut";
|
||||
public static final String EXTRA_LOCALE = "locale";
|
||||
public static final String EXTRA_ORIGINAL_WORD = "originalWord";
|
||||
public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut";
|
||||
|
||||
public static final int MODE_EDIT = 0;
|
||||
public static final int MODE_INSERT = 1;
|
||||
|
||||
/* package */ static final int CODE_WORD_ADDED = 0;
|
||||
/* package */ static final int CODE_CANCEL = 1;
|
||||
/* package */ static final int CODE_ALREADY_PRESENT = 2;
|
||||
|
||||
private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250;
|
||||
|
||||
private final int mMode; // Either MODE_EDIT or MODE_INSERT
|
||||
private final EditText mWordEditText;
|
||||
private final EditText mShortcutEditText;
|
||||
private String mLocale;
|
||||
private final String mOldWord;
|
||||
private final String mOldShortcut;
|
||||
private String mSavedWord;
|
||||
private String mSavedShortcut;
|
||||
|
||||
/* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
|
||||
mWordEditText = view.findViewById(R.id.user_dictionary_add_word_text);
|
||||
mShortcutEditText = view.findViewById(R.id.user_dictionary_add_shortcut);
|
||||
if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
|
||||
mShortcutEditText.setVisibility(View.GONE);
|
||||
view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE);
|
||||
}
|
||||
final String word = args.getString(EXTRA_WORD);
|
||||
if (null != word) {
|
||||
mWordEditText.setText(word);
|
||||
// Use getText in case the edit text modified the text we set. This happens when
|
||||
// it's too long to be edited.
|
||||
mWordEditText.setSelection(mWordEditText.getText().length());
|
||||
}
|
||||
if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
|
||||
final String shortcut = args.getString(EXTRA_SHORTCUT);
|
||||
if (null != shortcut && null != mShortcutEditText) {
|
||||
mShortcutEditText.setText(shortcut);
|
||||
}
|
||||
mOldShortcut = args.getString(EXTRA_SHORTCUT);
|
||||
} else {
|
||||
mOldShortcut = null;
|
||||
}
|
||||
mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT
|
||||
mOldWord = args.getString(EXTRA_WORD);
|
||||
updateLocale(args.getString(EXTRA_LOCALE));
|
||||
}
|
||||
|
||||
/* package */ UserDictionaryAddWordContents(final View view,
|
||||
final UserDictionaryAddWordContents oldInstanceToBeEdited) {
|
||||
mWordEditText = view.findViewById(R.id.user_dictionary_add_word_text);
|
||||
mShortcutEditText = view.findViewById(R.id.user_dictionary_add_shortcut);
|
||||
mMode = MODE_EDIT;
|
||||
mOldWord = oldInstanceToBeEdited.mSavedWord;
|
||||
mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
|
||||
updateLocale(mLocale);
|
||||
}
|
||||
|
||||
// locale may be null, this means default locale
|
||||
// It may also be the empty string, which means "all locales"
|
||||
/* package */ void updateLocale(final String locale) {
|
||||
mLocale = null == locale ? Locale.getDefault().toString() : locale;
|
||||
}
|
||||
|
||||
/* package */ void saveStateIntoBundle(final Bundle outState) {
|
||||
outState.putString(EXTRA_WORD, mWordEditText.getText().toString());
|
||||
outState.putString(EXTRA_ORIGINAL_WORD, mOldWord);
|
||||
if (null != mShortcutEditText) {
|
||||
outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString());
|
||||
}
|
||||
if (null != mOldShortcut) {
|
||||
outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut);
|
||||
}
|
||||
outState.putString(EXTRA_LOCALE, mLocale);
|
||||
}
|
||||
|
||||
/* package */ void delete(final Context context) {
|
||||
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
|
||||
// Mode edit: remove the old entry.
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
|
||||
}
|
||||
// If we are in add mode, nothing was added, so we don't need to do anything.
|
||||
}
|
||||
|
||||
/* package */
|
||||
int apply(final Context context, final Bundle outParameters) {
|
||||
if (null != outParameters) saveStateIntoBundle(outParameters);
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
|
||||
// Mode edit: remove the old entry.
|
||||
UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
|
||||
}
|
||||
final String newWord = mWordEditText.getText().toString();
|
||||
final String newShortcut;
|
||||
if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
|
||||
newShortcut = null;
|
||||
} else if (null == mShortcutEditText) {
|
||||
newShortcut = null;
|
||||
} else {
|
||||
final String tmpShortcut = mShortcutEditText.getText().toString();
|
||||
if (TextUtils.isEmpty(tmpShortcut)) {
|
||||
newShortcut = null;
|
||||
} else {
|
||||
newShortcut = tmpShortcut;
|
||||
}
|
||||
}
|
||||
if (TextUtils.isEmpty(newWord)) {
|
||||
// If the word is somehow empty, don't insert it.
|
||||
return CODE_CANCEL;
|
||||
}
|
||||
mSavedWord = newWord;
|
||||
mSavedShortcut = newShortcut;
|
||||
// If there is no shortcut, and the word already exists in the database, then we
|
||||
// should not insert, because either A. the word exists with no shortcut, in which
|
||||
// case the exact same thing we want to insert is already there, or B. the word
|
||||
// exists with at least one shortcut, in which case it has priority on our word.
|
||||
if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
|
||||
return CODE_ALREADY_PRESENT;
|
||||
}
|
||||
|
||||
// Disallow duplicates. If the same word with no shortcut is defined, remove it; if
|
||||
// the same word with the same shortcut is defined, remove it; but we don't mind if
|
||||
// there is the same word with a different, non-empty shortcut.
|
||||
UserDictionarySettings.deleteWord(newWord, null, resolver);
|
||||
if (!TextUtils.isEmpty(newShortcut)) {
|
||||
// If newShortcut is empty we just deleted this, no need to do it again
|
||||
UserDictionarySettings.deleteWord(newWord, newShortcut, resolver);
|
||||
}
|
||||
|
||||
// In this class we use the empty string to represent 'all locales' and mLocale cannot
|
||||
// be null. However the addWord method takes null to mean 'all locales'.
|
||||
UserDictionary.Words.addWord(context, newWord,
|
||||
FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ?
|
||||
null : LocaleUtils.constructLocaleFromString(mLocale));
|
||||
|
||||
return CODE_WORD_ADDED;
|
||||
}
|
||||
|
||||
private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD };
|
||||
private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD
|
||||
+ "=? AND " + UserDictionary.Words.LOCALE + "=?";
|
||||
private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD
|
||||
+ "=? AND " + UserDictionary.Words.LOCALE + " is null";
|
||||
private boolean hasWord(final String word, final Context context) {
|
||||
final Cursor cursor;
|
||||
// mLocale == "" indicates this is an entry for all languages. Here, mLocale can't
|
||||
// be null at all (it's ensured by the updateLocale method).
|
||||
if ("".equals(mLocale)) {
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES,
|
||||
new String[] { word }, null /* sort order */);
|
||||
} else {
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE,
|
||||
new String[] { word, mLocale }, null /* sort order */);
|
||||
}
|
||||
try {
|
||||
if (null == cursor) return false;
|
||||
return cursor.getCount() > 0;
|
||||
} finally {
|
||||
if (null != cursor) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocaleRenderer {
|
||||
private final String mLocaleString;
|
||||
private final String mDescription;
|
||||
|
||||
public LocaleRenderer(final Context context, @Nullable final String localeString) {
|
||||
mLocaleString = localeString;
|
||||
if (null == localeString) {
|
||||
mDescription = context.getString(R.string.user_dict_settings_more_languages);
|
||||
} else if ("".equals(localeString)) {
|
||||
mDescription = context.getString(R.string.user_dict_settings_all_languages);
|
||||
} else {
|
||||
mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDescription;
|
||||
}
|
||||
public String getLocaleString() {
|
||||
return mLocaleString;
|
||||
}
|
||||
// "More languages..." is null ; "All languages" is the empty string.
|
||||
public boolean isMoreLanguages() {
|
||||
return null == mLocaleString;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addLocaleDisplayNameToList(final Context context,
|
||||
final ArrayList<LocaleRenderer> list, final String locale) {
|
||||
if (null != locale) {
|
||||
list.add(new LocaleRenderer(context, locale));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get the list of locales to display for this word
|
||||
public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) {
|
||||
final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity);
|
||||
// Remove our locale if it's in, because we're always gonna put it at the top
|
||||
locales.remove(mLocale); // mLocale may not be null
|
||||
final String systemLocale = Locale.getDefault().toString();
|
||||
// The system locale should be inside. We want it at the 2nd spot.
|
||||
locales.remove(systemLocale); // system locale may not be null
|
||||
locales.remove(""); // Remove the empty string if it's there
|
||||
final ArrayList<LocaleRenderer> localesList = new ArrayList<>();
|
||||
// Add the passed locale, then the system locale at the top of the list. Add an
|
||||
// "all languages" entry at the bottom of the list.
|
||||
addLocaleDisplayNameToList(activity, localesList, mLocale);
|
||||
if (!systemLocale.equals(mLocale)) {
|
||||
addLocaleDisplayNameToList(activity, localesList, systemLocale);
|
||||
}
|
||||
for (final String l : locales) {
|
||||
// TODO: sort in unicode order
|
||||
addLocaleDisplayNameToList(activity, localesList, l);
|
||||
}
|
||||
if (!"".equals(mLocale)) {
|
||||
// If mLocale is "", then we already inserted the "all languages" item, so don't do it
|
||||
addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages
|
||||
}
|
||||
localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
|
||||
return localesList;
|
||||
}
|
||||
|
||||
public String getCurrentUserDictionaryLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.LocaleRenderer;
|
||||
import org.dslul.openboard.inputmethod.latin.userdictionary.UserDictionaryLocalePicker.LocationChangedListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
// Caveat: This class is basically taken from
|
||||
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
|
||||
// in order to deal with some devices that have issues with the user dictionary handling
|
||||
|
||||
/**
|
||||
* Fragment to add a word/shortcut to the user dictionary.
|
||||
*
|
||||
* As opposed to the UserDictionaryActivity, this is only invoked within Settings
|
||||
* from the UserDictionarySettings.
|
||||
*/
|
||||
public class UserDictionaryAddWordFragment extends Fragment
|
||||
implements AdapterView.OnItemSelectedListener, LocationChangedListener {
|
||||
|
||||
private static final int OPTIONS_MENU_ADD = Menu.FIRST;
|
||||
private static final int OPTIONS_MENU_DELETE = Menu.FIRST + 1;
|
||||
|
||||
private UserDictionaryAddWordContents mContents;
|
||||
private View mRootView;
|
||||
private boolean mIsDeleting = false;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setTitle(R.string.edit_personal_dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedState) {
|
||||
mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
|
||||
mIsDeleting = false;
|
||||
// If we have a non-null mContents object, it's the old value before a configuration
|
||||
// change (eg rotation) so we need to use its values. Otherwise, read from the arguments.
|
||||
if (null == mContents) {
|
||||
mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
|
||||
} else {
|
||||
// We create a new mContents object to account for the new situation : a word has
|
||||
// been added to the user dictionary when we started rotating, and we are now editing
|
||||
// it. That means in particular if the word undergoes any change, the old version should
|
||||
// be updated, so the mContents object needs to switch to EDIT mode if it was in
|
||||
// INSERT mode.
|
||||
mContents = new UserDictionaryAddWordContents(mRootView, mContents);
|
||||
}
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
|
||||
R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_plus);
|
||||
actionItemAdd.setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0,
|
||||
R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete);
|
||||
actionItemDelete.setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the framework when a menu option is pressed.
|
||||
*
|
||||
* @param item the item that was pressed
|
||||
* @return false to allow normal menu processing to proceed, true to consume it here
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == OPTIONS_MENU_ADD) {
|
||||
// added the entry in "onPause"
|
||||
requireActivity().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
if (item.getItemId() == OPTIONS_MENU_DELETE) {
|
||||
mContents.delete(getActivity());
|
||||
mIsDeleting = true;
|
||||
requireActivity().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// We are being shown: display the word
|
||||
updateSpinner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setTitle(R.string.user_dict_settings_add_dialog_title);
|
||||
actionBar.setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mContents.getCurrentUserDictionaryLocale()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setSubtitle(null);
|
||||
}
|
||||
|
||||
private void updateSpinner() {
|
||||
final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity());
|
||||
|
||||
final Spinner localeSpinner = mRootView.findViewById(R.id.user_dictionary_add_locale);
|
||||
final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
|
||||
requireActivity(), android.R.layout.simple_spinner_item, localesList);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
localeSpinner.setAdapter(adapter);
|
||||
localeSpinner.setOnItemSelectedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// We are being hidden: commit changes to the user dictionary, unless we were deleting it
|
||||
if (!mIsDeleting) {
|
||||
mContents.apply(getActivity(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(final AdapterView<?> parent, final View view, final int pos,
|
||||
final long id) {
|
||||
final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos);
|
||||
if (locale.isMoreLanguages()) {
|
||||
AppCompatActivity activity = (AppCompatActivity) requireActivity();
|
||||
activity.getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new UserDictionaryLocalePicker())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
} else {
|
||||
mContents.updateLocale(locale.getLocaleString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(final AdapterView<?> parent) {
|
||||
// I'm not sure we can come here, but if we do, that's the right thing to do.
|
||||
final Bundle args = getArguments();
|
||||
if (args == null) return;
|
||||
mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE));
|
||||
}
|
||||
|
||||
// Called by the locale picker
|
||||
@Override
|
||||
public void onLocaleSelected(final Locale locale) {
|
||||
mContents.updateLocale(locale.toString());
|
||||
requireActivity().onBackPressed();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.UserDictionary;
|
||||
import android.text.TextUtils;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
// Caveat: This class is basically taken from
|
||||
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java
|
||||
// in order to deal with some devices that have issues with the user dictionary handling
|
||||
|
||||
public class UserDictionaryList extends PreferenceFragmentCompat {
|
||||
|
||||
public static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION =
|
||||
"android.settings.USER_DICTIONARY_SETTINGS";
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle bundle, @Nullable String s) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
getPreferenceManager().setStorageDeviceProtected();
|
||||
}
|
||||
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
|
||||
}
|
||||
|
||||
public static TreeSet<String> getUserDictionaryLocalesSet(final Activity activity) {
|
||||
final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
new String[] { UserDictionary.Words.LOCALE },
|
||||
null, null, null);
|
||||
final TreeSet<String> localeSet = new TreeSet<>();
|
||||
if (null == cursor) {
|
||||
// The user dictionary service is not present or disabled. Return null.
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
|
||||
do {
|
||||
final String locale = cursor.getString(columnIndex);
|
||||
localeSet.add(null != locale ? locale : "");
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
|
||||
// For ICS, we need to show "For all languages" in case that the keyboard locale
|
||||
// is different from the system locale
|
||||
localeSet.add("");
|
||||
}
|
||||
|
||||
final InputMethodManager imm =
|
||||
(InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
final List<InputMethodInfo> imis = imm.getEnabledInputMethodList();
|
||||
for (final InputMethodInfo imi : imis) {
|
||||
final List<InputMethodSubtype> subtypes =
|
||||
imm.getEnabledInputMethodSubtypeList(
|
||||
imi, true /* allowsImplicitlySelectedSubtypes */);
|
||||
for (InputMethodSubtype subtype : subtypes) {
|
||||
final String locale = subtype.getLocale();
|
||||
if (!TextUtils.isEmpty(locale)) {
|
||||
localeSet.add(locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We come here after we have collected locales from existing user dictionary entries and
|
||||
// enabled subtypes. If we already have the locale-without-country version of the system
|
||||
// locale, we don't add the system locale to avoid confusion even though it's technically
|
||||
// correct to add it.
|
||||
if (!localeSet.contains(Locale.getDefault().getLanguage())) {
|
||||
localeSet.add(Locale.getDefault().toString());
|
||||
}
|
||||
|
||||
return localeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entries that allow the user to go into the user dictionary for each locale.
|
||||
* @param userDictGroup The group to put the settings in.
|
||||
*/
|
||||
protected void createUserDictSettings(final PreferenceGroup userDictGroup) {
|
||||
userDictGroup.removeAll();
|
||||
final TreeSet<String> localeSet = UserDictionaryList.getUserDictionaryLocalesSet(requireActivity());
|
||||
if (localeSet == null) {
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(null));
|
||||
return;
|
||||
}
|
||||
|
||||
if (localeSet.size() > 1) {
|
||||
// Have an "All languages" entry in the languages list if there are two or more active
|
||||
// languages
|
||||
localeSet.add("");
|
||||
}
|
||||
|
||||
if (localeSet.isEmpty()) {
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(null));
|
||||
} else {
|
||||
for (String locale : localeSet) {
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(locale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single User Dictionary Preference object, with its parameters set.
|
||||
* @param localeString The locale for which this user dictionary is for.
|
||||
* @return The corresponding preference.
|
||||
*/
|
||||
protected Preference createUserDictionaryPreference(@Nullable final String localeString) {
|
||||
final Preference newPref = new Preference(requireContext());
|
||||
final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
|
||||
if (null == localeString) {
|
||||
newPref.setTitle(Locale.getDefault().getDisplayName());
|
||||
} else {
|
||||
if (localeString.isEmpty()) {
|
||||
newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
|
||||
} else {
|
||||
newPref.setTitle(
|
||||
LocaleUtils.constructLocaleFromString(localeString).getDisplayName());
|
||||
}
|
||||
intent.putExtra("locale", localeString);
|
||||
newPref.getExtras().putString("locale", localeString);
|
||||
}
|
||||
newPref.setIntent(intent);
|
||||
newPref.setFragment(UserDictionarySettings.class.getName());
|
||||
return newPref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
createUserDictSettings(getPreferenceScreen());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
// Caveat: This class is basically taken from
|
||||
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java
|
||||
// in order to deal with some devices that have issues with the user dictionary handling
|
||||
|
||||
public class UserDictionaryLocalePicker extends Fragment {
|
||||
public UserDictionaryLocalePicker() {
|
||||
super();
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public interface LocationChangedListener {
|
||||
void onLocaleSelected(Locale locale);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.userdictionary;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities of the user dictionary settings
|
||||
* TODO: We really want to move these utilities to a static library.
|
||||
*/
|
||||
public class UserDictionarySettingsUtils {
|
||||
public static String getLocaleDisplayName(Context context, String localeStr) {
|
||||
if (TextUtils.isEmpty(localeStr)) {
|
||||
// CAVEAT: localeStr should not be null because a null locale stands for the system
|
||||
// locale in UserDictionary.Words.addWord.
|
||||
return context.getResources().getString(R.string.user_dict_settings_all_languages);
|
||||
}
|
||||
final Locale locale = LocaleUtils.constructLocaleFromString(localeStr);
|
||||
final Locale systemLocale = context.getResources().getConfiguration().locale;
|
||||
return locale.getDisplayName(systemLocale);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue