mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-06-09 16:17:44 +00:00
Remove old settings and clean up (#1392)
This commit is contained in:
parent
8a8e8a70e3
commit
f929caa4d8
152 changed files with 263 additions and 9262 deletions
|
@ -94,6 +94,7 @@ public final class BinaryDictionaryUtils {
|
|||
valueArray);
|
||||
}
|
||||
|
||||
/** normalized score is >= 0, with 0 being a bad match, ~0.1 ok for autocorrect, and ~1.5 a very good match */
|
||||
public static float calcNormalizedScore(final String before, final String after,
|
||||
final int score) {
|
||||
return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
|
||||
|
|
|
@ -88,8 +88,6 @@ public class Keyboard {
|
|||
|
||||
@NonNull
|
||||
private final ProximityInfo mProximityInfo;
|
||||
@NonNull
|
||||
private final KeyboardLayout mKeyboardLayout;
|
||||
|
||||
private final boolean mProximityCharsCorrectionEnabled;
|
||||
|
||||
|
@ -117,8 +115,6 @@ public class Keyboard {
|
|||
mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
|
||||
mSortedKeys, params.mTouchPositionCorrection);
|
||||
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
|
||||
mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth,
|
||||
mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight);
|
||||
}
|
||||
|
||||
protected Keyboard(@NonNull final Keyboard keyboard) {
|
||||
|
@ -143,7 +139,6 @@ public class Keyboard {
|
|||
|
||||
mProximityInfo = keyboard.mProximityInfo;
|
||||
mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
|
||||
mKeyboardLayout = keyboard.mKeyboardLayout;
|
||||
}
|
||||
|
||||
public boolean hasProximityCharsCorrection(final int code) {
|
||||
|
@ -163,11 +158,6 @@ public class Keyboard {
|
|||
return mProximityInfo;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public KeyboardLayout getKeyboardLayout() {
|
||||
return mKeyboardLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sorted list of keys of this keyboard.
|
||||
* The keys are sorted from top-left to bottom-right order.
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.keyboard;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* KeyboardLayout maintains the keyboard layout information.
|
||||
*/
|
||||
public class KeyboardLayout {
|
||||
|
||||
private final int[] mKeyCodes;
|
||||
|
||||
private final int[] mKeyXCoordinates;
|
||||
private final int[] mKeyYCoordinates;
|
||||
|
||||
private final int[] mKeyWidths;
|
||||
private final int[] mKeyHeights;
|
||||
|
||||
public final int mMostCommonKeyWidth;
|
||||
public final int mMostCommonKeyHeight;
|
||||
|
||||
public final int mKeyboardWidth;
|
||||
public final int mKeyboardHeight;
|
||||
|
||||
public KeyboardLayout(ArrayList<Key> layoutKeys, int mostCommonKeyWidth,
|
||||
int mostCommonKeyHeight, int keyboardWidth, int keyboardHeight) {
|
||||
mMostCommonKeyWidth = mostCommonKeyWidth;
|
||||
mMostCommonKeyHeight = mostCommonKeyHeight;
|
||||
mKeyboardWidth = keyboardWidth;
|
||||
mKeyboardHeight = keyboardHeight;
|
||||
|
||||
mKeyCodes = new int[layoutKeys.size()];
|
||||
mKeyXCoordinates = new int[layoutKeys.size()];
|
||||
mKeyYCoordinates = new int[layoutKeys.size()];
|
||||
mKeyWidths = new int[layoutKeys.size()];
|
||||
mKeyHeights = new int[layoutKeys.size()];
|
||||
|
||||
for (int i = 0; i < layoutKeys.size(); i++) {
|
||||
Key key = layoutKeys.get(i);
|
||||
mKeyCodes[i] = Character.toLowerCase(key.getCode());
|
||||
mKeyXCoordinates[i] = key.getX();
|
||||
mKeyYCoordinates[i] = key.getY();
|
||||
mKeyWidths[i] = key.getWidth();
|
||||
mKeyHeights[i] = key.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getKeyCodes() {
|
||||
return mKeyCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The x-coordinate for the top-left corner of the keys.
|
||||
*
|
||||
*/
|
||||
public int[] getKeyXCoordinates() {
|
||||
return mKeyXCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* The y-coordinate for the top-left corner of the keys.
|
||||
*/
|
||||
public int[] getKeyYCoordinates() {
|
||||
return mKeyYCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* The widths of the keys which are smaller than the true hit-area due to the gaps
|
||||
* between keys. The mostCommonKey(Width/Height) represents the true key width/height
|
||||
* including the gaps.
|
||||
*/
|
||||
public int[] getKeyWidths() {
|
||||
return mKeyWidths;
|
||||
}
|
||||
|
||||
/**
|
||||
* The heights of the keys which are smaller than the true hit-area due to the gaps
|
||||
* between keys. The mostCommonKey(Width/Height) represents the true key width/height
|
||||
* including the gaps.
|
||||
*/
|
||||
public int[] getKeyHeights() {
|
||||
return mKeyHeights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create {@link KeyboardLayout} objects.
|
||||
*/
|
||||
public static KeyboardLayout newKeyboardLayout(@NonNull final List<Key> sortedKeys,
|
||||
int mostCommonKeyWidth, int mostCommonKeyHeight,
|
||||
int occupiedWidth, int occupiedHeight) {
|
||||
final ArrayList<Key> layoutKeys = new ArrayList<Key>();
|
||||
for (final Key key : sortedKeys) {
|
||||
if (!ProximityInfo.needsProximityInfo(key)) {
|
||||
continue;
|
||||
}
|
||||
if (key.getCode() != ',') {
|
||||
layoutKeys.add(key);
|
||||
}
|
||||
}
|
||||
return new KeyboardLayout(layoutKeys, mostCommonKeyWidth, mostCommonKeyHeight, occupiedWidth, occupiedHeight);
|
||||
}
|
||||
}
|
|
@ -114,18 +114,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
}
|
||||
}
|
||||
|
||||
// todo: maybe we can remove this after removing old setting?
|
||||
public void forceUpdateKeyboardTheme(@NonNull Context displayContext) {
|
||||
Settings settings = Settings.getInstance();
|
||||
settings.loadSettings(displayContext, settings.getCurrent().mLocale, settings.getCurrent().mInputAttributes);
|
||||
final boolean showing = mLatinIME.isInputViewShown();
|
||||
if (showing)
|
||||
mLatinIME.hideWindow();
|
||||
mLatinIME.setInputView(onCreateInputView(displayContext, mIsHardwareAcceleratedDrawingEnabled));
|
||||
if (showing)
|
||||
mLatinIME.showWindow(true);
|
||||
}
|
||||
|
||||
private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context, final KeyboardTheme keyboardTheme) {
|
||||
final Resources res = context.getResources();
|
||||
if (mThemeNeedsReload
|
||||
|
@ -739,6 +727,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
// Hide and show IME, showing will trigger the reload.
|
||||
// Reloading while IME is shown is glitchy, and hiding / showing is so fast the user shouldn't notice.
|
||||
mLatinIME.hideWindow();
|
||||
mLatinIME.showWindow(true);
|
||||
try {
|
||||
mLatinIME.showWindow(true);
|
||||
} catch (IllegalStateException e) {
|
||||
// in tests isInputViewShown returns true, but showWindow throws "IllegalStateException: Window token is not set yet."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ class ClipboardHistoryRecyclerView @JvmOverloads constructor(
|
|||
|
||||
var placeholderView: View? = null
|
||||
val historyManager: ClipboardHistoryManager? get() = (adapter as? ClipboardAdapter?)?.clipboardHistoryManager
|
||||
@Suppress("unused")
|
||||
private val touchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false
|
||||
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: ViewHolder): Int {
|
||||
|
|
|
@ -13,7 +13,6 @@ import android.content.res.TypedArray;
|
|||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import helium314.keyboard.latin.common.DefaultColors;
|
||||
import helium314.keyboard.latin.settings.Defaults;
|
||||
import helium314.keyboard.latin.utils.KtxKt;
|
||||
import helium314.keyboard.latin.utils.Log;
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.keyboard.internal;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||
import helium314.keyboard.latin.common.Constants;
|
||||
import helium314.keyboard.latin.common.StringUtils;
|
||||
|
||||
/**
|
||||
* The string parser of codesArray specification for <GridRows />. The attribute codesArray is an
|
||||
* array of string.
|
||||
* Each element of the array defines a key label by specifying a code point as a hexadecimal string.
|
||||
* A key label may consist of multiple code points separated by comma.
|
||||
* Each element of the array optionally can have an output text definition after vertical bar
|
||||
* marker. An output text may consist of multiple code points separated by comma.
|
||||
* The format of the codesArray element should be:
|
||||
* <pre>
|
||||
* label1[,label2]*(|outputText1[,outputText2]*(|minSupportSdkVersion)?)?
|
||||
* </pre>
|
||||
*/
|
||||
// TODO: Write unit tests for this class.
|
||||
public final class CodesArrayParser {
|
||||
// Constants for parsing.
|
||||
private static final char COMMA = Constants.CODE_COMMA;
|
||||
private static final String COMMA_REGEX = StringUtils.newSingleCodePointString(COMMA);
|
||||
private static final String VERTICAL_BAR_REGEX = // "\\|"
|
||||
new String(new char[] { Constants.CODE_BACKSLASH, Constants.CODE_VERTICAL_BAR });
|
||||
private static final int BASE_HEX = 16;
|
||||
|
||||
private CodesArrayParser() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
private static String getLabelSpec(final String codesArraySpec) {
|
||||
final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
|
||||
if (strs.length <= 1) {
|
||||
return codesArraySpec;
|
||||
}
|
||||
return strs[0];
|
||||
}
|
||||
|
||||
public static String parseLabel(final String codesArraySpec) {
|
||||
final String labelSpec = getLabelSpec(codesArraySpec);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String codeInHex : labelSpec.split(COMMA_REGEX)) {
|
||||
final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
|
||||
sb.appendCodePoint(codePoint);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String getCodeSpec(final String codesArraySpec) {
|
||||
final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
|
||||
if (strs.length <= 1) {
|
||||
return codesArraySpec;
|
||||
}
|
||||
return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1];
|
||||
}
|
||||
|
||||
public static int getMinSupportSdkVersion(final String codesArraySpec) {
|
||||
final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
|
||||
if (strs.length <= 2) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(strs[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int parseCode(final String codesArraySpec) {
|
||||
final String codeSpec = getCodeSpec(codesArraySpec);
|
||||
if (codeSpec.indexOf(COMMA) < 0) {
|
||||
return Integer.parseInt(codeSpec, BASE_HEX);
|
||||
}
|
||||
return KeyCode.MULTIPLE_CODE_POINTS;
|
||||
}
|
||||
|
||||
public static String parseOutputText(final String codesArraySpec) {
|
||||
final String codeSpec = getCodeSpec(codesArraySpec);
|
||||
if (codeSpec.indexOf(COMMA) < 0) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String codeInHex : codeSpec.split(COMMA_REGEX)) {
|
||||
final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
|
||||
sb.appendCodePoint(codePoint);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.keyboard.internal;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import helium314.keyboard.latin.common.StringUtils;
|
||||
|
||||
/**
|
||||
* The string parser of moreCodesArray specification for <GridRows />. The attribute moreCodesArray is an
|
||||
* array of string.
|
||||
* The more codes array specification is semicolon separated "codes array specification" each of which represents one
|
||||
* "popup key".
|
||||
* Each element of the array defines a sequence of key labels specified as hexadecimal strings
|
||||
* representing code points separated by a vertical bar.
|
||||
*
|
||||
*/
|
||||
public final class MoreCodesArrayParser {
|
||||
// Constants for parsing.
|
||||
private static final char SEMICOLON = ';';
|
||||
private static final String SEMICOLON_REGEX = StringUtils.newSingleCodePointString(SEMICOLON);
|
||||
|
||||
private MoreCodesArrayParser() {
|
||||
// This utility class is not publicly instantiable.
|
||||
}
|
||||
|
||||
public static String parseKeySpecs(@Nullable String codeArraySpecs) {
|
||||
if (codeArraySpecs == null || TextUtils.isEmpty(codeArraySpecs)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String codeArraySpec : codeArraySpecs.split(SEMICOLON_REGEX)) {
|
||||
final int supportedMinSdkVersion = CodesArrayParser.getMinSupportSdkVersion(codeArraySpec);
|
||||
if (Build.VERSION.SDK_INT < supportedMinSdkVersion) {
|
||||
continue;
|
||||
}
|
||||
final String label = CodesArrayParser.parseLabel(codeArraySpec);
|
||||
final String outputText = CodesArrayParser.parseOutputText(codeArraySpec);
|
||||
|
||||
sb.append(label).append("|").append(outputText);
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
if (sb.length() > 0) sb.deleteCharAt(sb.length() - 1);
|
||||
|
||||
return sb.length() > 0 ? sb.toString() : null;
|
||||
}
|
||||
}
|
|
@ -332,15 +332,3 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
const val LAYOUT_SYMBOLS = "symbols"
|
||||
const val LAYOUT_SYMBOLS_SHIFTED = "symbols_shifted"
|
||||
const val LAYOUT_SYMBOLS_ARABIC = "symbols_arabic"
|
||||
const val LAYOUT_NUMPAD = "numpad"
|
||||
const val LAYOUT_NUMPAD_LANDSCAPE = "numpad_landscape"
|
||||
const val LAYOUT_NUMBER = "number"
|
||||
const val LAYOUT_PHONE = "phone"
|
||||
const val LAYOUT_PHONE_SYMBOLS = "phone_symbols"
|
||||
const val LAYOUT_NUMBER_ROW = "number_row"
|
||||
const val LAYOUT_EMOJI_BOTTOM_ROW = "emoji_bottom_row"
|
||||
const val LAYOUT_CLIPBOARD_BOTTOM_ROW = "clip_bottom_row"
|
||||
|
|
|
@ -516,6 +516,16 @@ fun checkVersionUpgrade(context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (oldVersion <= 2309) {
|
||||
if (prefs.contains("auto_correction_confidence")) {
|
||||
val value = when (prefs.getString("auto_correction_confidence", "0")) {
|
||||
"1" -> 0.067f
|
||||
"2" -> -1f
|
||||
else -> 0.185f
|
||||
}
|
||||
prefs.edit().remove("auto_correction_confidence").putFloat(Settings.PREF_AUTO_CORRECT_THRESHOLD, value).apply()
|
||||
}
|
||||
}
|
||||
upgradeToolbarPrefs(prefs)
|
||||
LayoutUtilsCustom.onLayoutFileChanged() // just to be sure
|
||||
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||
|
|
|
@ -23,23 +23,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
*/
|
||||
public final class DictionaryCollection extends Dictionary {
|
||||
private final String TAG = DictionaryCollection.class.getSimpleName();
|
||||
protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
|
||||
|
||||
public DictionaryCollection(final String dictType, final Locale locale) {
|
||||
super(dictType, locale);
|
||||
mDictionaries = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
|
||||
public DictionaryCollection(final String dictType, final Locale locale,
|
||||
final Dictionary... dictionaries) {
|
||||
super(dictType, locale);
|
||||
if (null == dictionaries) {
|
||||
mDictionaries = new CopyOnWriteArrayList<>();
|
||||
} else {
|
||||
mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
|
||||
mDictionaries.removeAll(Collections.singleton(null));
|
||||
}
|
||||
}
|
||||
private final CopyOnWriteArrayList<Dictionary> mDictionaries;
|
||||
|
||||
public DictionaryCollection(final String dictType, final Locale locale,
|
||||
final Collection<Dictionary> dictionaries) {
|
||||
|
|
|
@ -70,7 +70,6 @@ import helium314.keyboard.latin.common.ViewOutlineProviderUtilsKt;
|
|||
import helium314.keyboard.latin.define.DebugFlags;
|
||||
import helium314.keyboard.latin.define.ProductionFlags;
|
||||
import helium314.keyboard.latin.inputlogic.InputLogic;
|
||||
import helium314.keyboard.latin.permissions.PermissionsManager;
|
||||
import helium314.keyboard.latin.personalization.PersonalizationHelper;
|
||||
import helium314.keyboard.latin.settings.Settings;
|
||||
import helium314.keyboard.latin.settings.SettingsValues;
|
||||
|
@ -109,8 +108,7 @@ import androidx.core.content.ContextCompat;
|
|||
*/
|
||||
public class LatinIME extends InputMethodService implements
|
||||
SuggestionStripView.Listener, SuggestionStripViewAccessor,
|
||||
DictionaryFacilitator.DictionaryInitializationListener,
|
||||
PermissionsManager.PermissionsResultCallback {
|
||||
DictionaryFacilitator.DictionaryInitializationListener {
|
||||
static final String TAG = LatinIME.class.getSimpleName();
|
||||
private static final boolean TRACE = false;
|
||||
|
||||
|
@ -1379,11 +1377,6 @@ public class LatinIME extends InputMethodService implements
|
|||
return keyboard.getCoordinates(codePoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(boolean allGranted) {
|
||||
setNeutralSuggestionStrip();
|
||||
}
|
||||
|
||||
public void displaySettingsDialog() {
|
||||
launchSettings();
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ import android.view.inputmethod.InputMethodManager;
|
|||
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet;
|
||||
import helium314.keyboard.latin.settings.Settings;
|
||||
import helium314.keyboard.latin.setup.SetupActivity;
|
||||
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils;
|
||||
import helium314.keyboard.settings.SettingsActivity;
|
||||
|
||||
/**
|
||||
* This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
|
||||
|
@ -89,7 +89,7 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
|
|||
return; // can't change visibility in Android 10 and above
|
||||
final SharedPreferences prefs = KtxKt.prefs(context);
|
||||
context.getPackageManager().setComponentEnabledSetting(
|
||||
new ComponentName(context, SetupActivity.class),
|
||||
new ComponentName(context, SettingsActivity.class),
|
||||
Settings.readShowSetupWizardIcon(prefs, context)
|
||||
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
|
|
|
@ -181,11 +181,6 @@ object LocaleUtils {
|
|||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLocaleDisplayNameInSystemLocale(locale: Locale, context: Context): String {
|
||||
return getLocaleDisplayNameInLocale(locale, context.resources, context.resources.configuration.locale())
|
||||
}
|
||||
|
||||
fun Locale.localizedDisplayName(context: Context) =
|
||||
getLocaleDisplayNameInLocale(this, context.resources, context.resources.configuration.locale())
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import androidx.annotation.Nullable;
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||
import helium314.keyboard.latin.utils.ScriptUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -22,9 +21,6 @@ public final class StringUtils {
|
|||
public static final int CAPITALIZE_FIRST = 1; // First only
|
||||
public static final int CAPITALIZE_ALL = 2; // All caps
|
||||
|
||||
@NonNull
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private static final char CHAR_LINE_FEED = 0X000A;
|
||||
private static final char CHAR_VERTICAL_TAB = 0X000B;
|
||||
private static final char CHAR_FORM_FEED = 0X000C;
|
||||
|
@ -50,61 +46,6 @@ public final class StringUtils {
|
|||
return (str == null || str.length() == 0);
|
||||
}
|
||||
|
||||
// Taken from android.text.TextUtils to cut the dependency to the Android framework.
|
||||
|
||||
/**
|
||||
* Returns a string containing the tokens joined by delimiters.
|
||||
*
|
||||
* @param delimiter the delimiter
|
||||
* @param tokens an array objects to be joined. Strings will be formed from
|
||||
* the objects by calling object.toString().
|
||||
*/
|
||||
@NonNull
|
||||
public static String join(@NonNull final CharSequence delimiter,
|
||||
@NonNull final Iterable<?> tokens) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean firstTime = true;
|
||||
for (final Object token : tokens) {
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
} else {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
sb.append(token);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Taken from android.text.TextUtils to cut the dependency to the Android framework.
|
||||
|
||||
/**
|
||||
* Returns true if a and b are equal, including if they are both null.
|
||||
* <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
|
||||
* both the arguments were instances of String.</i></p>
|
||||
*
|
||||
* @param a first CharSequence to check
|
||||
* @param b second CharSequence to check
|
||||
* @return true if a and b are equal
|
||||
*/
|
||||
public static boolean equals(@Nullable final CharSequence a, @Nullable final CharSequence b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
final int length;
|
||||
if (a != null && b != null && (length = a.length()) == b.length()) {
|
||||
if (a instanceof String && b instanceof String) {
|
||||
return a.equals(b);
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (a.charAt(i) != b.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int codePointCount(@Nullable final CharSequence text) {
|
||||
if (isEmpty(text)) {
|
||||
return 0;
|
||||
|
@ -123,33 +64,6 @@ public final class StringUtils {
|
|||
return new String(Character.toChars(codePoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicates from an array of strings.
|
||||
* <p>
|
||||
* This method will always keep the first occurrence of all strings at their position
|
||||
* in the array, removing the subsequent ones.
|
||||
*/
|
||||
public static void removeDupes(@NonNull final ArrayList<String> suggestions) {
|
||||
if (suggestions.size() < 2) {
|
||||
return;
|
||||
}
|
||||
int i = 1;
|
||||
// Don't cache suggestions.size(), since we may be removing items
|
||||
while (i < suggestions.size()) {
|
||||
final String cur = suggestions.get(i);
|
||||
// Compare each suggestion with each previous suggestion
|
||||
for (int j = 0; j < i; j++) {
|
||||
final String previous = suggestions.get(j);
|
||||
if (equals(cur, previous)) {
|
||||
suggestions.remove(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String capitalizeFirstCodePoint(@NonNull final String s,
|
||||
@NonNull final Locale locale) {
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.permissions;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
/**
|
||||
* An activity to help request permissions. It's used when no other activity is available, e.g. in
|
||||
* InputMethodService. This activity assumes that all permissions are not granted yet.
|
||||
*/
|
||||
public final class PermissionsActivity
|
||||
extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
||||
/**
|
||||
* Key to retrieve requested permissions from the intent.
|
||||
*/
|
||||
public static final String EXTRA_PERMISSION_REQUESTED_PERMISSIONS = "requested_permissions";
|
||||
|
||||
/**
|
||||
* Key to retrieve request code from the intent.
|
||||
*/
|
||||
public static final String EXTRA_PERMISSION_REQUEST_CODE = "request_code";
|
||||
|
||||
private static final int INVALID_REQUEST_CODE = -1;
|
||||
|
||||
private int mPendingRequestCode = INVALID_REQUEST_CODE;
|
||||
|
||||
/**
|
||||
* Starts a PermissionsActivity and checks/requests supplied permissions.
|
||||
*/
|
||||
public static void run(
|
||||
@NonNull Context context, int requestCode, @NonNull String... permissionStrings) {
|
||||
Intent intent = new Intent(context.getApplicationContext(), PermissionsActivity.class);
|
||||
intent.putExtra(EXTRA_PERMISSION_REQUESTED_PERMISSIONS, permissionStrings);
|
||||
intent.putExtra(EXTRA_PERMISSION_REQUEST_CODE, requestCode);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mPendingRequestCode = (savedInstanceState != null)
|
||||
? savedInstanceState.getInt(EXTRA_PERMISSION_REQUEST_CODE, INVALID_REQUEST_CODE)
|
||||
: INVALID_REQUEST_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(EXTRA_PERMISSION_REQUEST_CODE, mPendingRequestCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Only do request when there is no pending request to avoid duplicated requests.
|
||||
if (mPendingRequestCode == INVALID_REQUEST_CODE) {
|
||||
final Bundle extras = getIntent().getExtras();
|
||||
if (extras == null) return;
|
||||
final String[] permissionsToRequest = extras.getStringArray(EXTRA_PERMISSION_REQUESTED_PERMISSIONS);
|
||||
mPendingRequestCode = extras.getInt(EXTRA_PERMISSION_REQUEST_CODE);
|
||||
// Assuming that all supplied permissions are not granted yet, so that we don't need to
|
||||
// check them again.
|
||||
PermissionsUtil.requestPermissions(this, mPendingRequestCode, permissionsToRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
mPendingRequestCode = INVALID_REQUEST_CODE;
|
||||
PermissionsManager.get(this).onRequestPermissionsResult(
|
||||
requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.permissions;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manager to perform permission related tasks. Always call on the UI thread.
|
||||
*/
|
||||
public class PermissionsManager {
|
||||
|
||||
public interface PermissionsResultCallback {
|
||||
void onRequestPermissionsResult(boolean allGranted);
|
||||
}
|
||||
|
||||
private int mRequestCodeId;
|
||||
|
||||
private final Context mContext;
|
||||
private final Map<Integer, PermissionsResultCallback> mRequestIdToCallback = new HashMap<>();
|
||||
|
||||
private static PermissionsManager sInstance;
|
||||
|
||||
public PermissionsManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized PermissionsManager get(@NonNull Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new PermissionsManager(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private synchronized int getNextRequestId() {
|
||||
return ++mRequestCodeId;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void requestPermissions(@NonNull PermissionsResultCallback callback,
|
||||
@Nullable Activity activity,
|
||||
String... permissionsToRequest) {
|
||||
List<String> deniedPermissions = PermissionsUtil.getDeniedPermissions(
|
||||
mContext, permissionsToRequest);
|
||||
if (deniedPermissions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// otherwise request the permissions.
|
||||
int requestId = getNextRequestId();
|
||||
String[] permissionsArray = deniedPermissions.toArray(
|
||||
new String[deniedPermissions.size()]);
|
||||
|
||||
mRequestIdToCallback.put(requestId, callback);
|
||||
if (activity != null) {
|
||||
PermissionsUtil.requestPermissions(activity, requestId, permissionsArray);
|
||||
} else {
|
||||
PermissionsActivity.run(mContext, requestId, permissionsArray);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void onRequestPermissionsResult(
|
||||
int requestCode, String[] permissions, int[] grantResults) {
|
||||
PermissionsResultCallback permissionsResultCallback = mRequestIdToCallback.get(requestCode);
|
||||
mRequestIdToCallback.remove(requestCode);
|
||||
|
||||
boolean allGranted = PermissionsUtil.allGranted(grantResults);
|
||||
permissionsResultCallback.onRequestPermissionsResult(allGranted);
|
||||
}
|
||||
}
|
|
@ -6,70 +6,22 @@
|
|||
|
||||
package helium314.keyboard.latin.permissions;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* Utility class for permissions.
|
||||
*/
|
||||
public class PermissionsUtil {
|
||||
|
||||
/**
|
||||
* Returns the list of permissions not granted from the given list of permissions.
|
||||
* @param context Context
|
||||
* @param permissions list of permissions to check.
|
||||
* @return the list of permissions that do not have permission to use.
|
||||
*/
|
||||
public static List<String> getDeniedPermissions(Context context,
|
||||
String... permissions) {
|
||||
final List<String> deniedPermissions = new ArrayList<>();
|
||||
for (String permission : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(context, permission)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
deniedPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
return deniedPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given activity and requests the user for permissions.
|
||||
* @param activity activity to use.
|
||||
* @param requestCode request code/id to use.
|
||||
* @param permissions String array of permissions that needs to be requested.
|
||||
*/
|
||||
public static void requestPermissions(Activity activity, int requestCode,
|
||||
String[] permissions) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all the permissions are granted.
|
||||
*/
|
||||
public static boolean allGranted(@NonNull int[] grantResults) {
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries if al the permissions are granted for the given permission strings.
|
||||
*/
|
||||
public static boolean checkAllPermissionsGranted(Context context, String... permissions) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
// For all pre-M devices, we should have all the premissions granted on install.
|
||||
// For all pre-M devices, we should have all the permissions granted on install.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.Log
|
||||
import helium314.keyboard.latin.utils.SpannableStringUtils
|
||||
|
||||
/**
|
||||
* "About" sub screen.
|
||||
*/
|
||||
class AboutFragment : SubScreenFragment() {
|
||||
override fun onCreate(icicle: Bundle?) {
|
||||
super.onCreate(icicle)
|
||||
addPreferencesFromResource(R.xml.prefs_screen_about)
|
||||
|
||||
setupHiddenFeatures()
|
||||
setupVersionPref()
|
||||
findPreference<Preference>("log_reader")?.setOnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(Intent.EXTRA_TITLE,
|
||||
requireContext().getString(R.string.english_ime_name)
|
||||
.replace(" ", "_") + "_log_${System.currentTimeMillis()}.txt"
|
||||
)
|
||||
.setType("text/plain")
|
||||
logFilePicker.launch(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private val logFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = result.data?.data ?: return@registerForActivityResult
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(Log.getLog().joinToString("\n")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHiddenFeatures() {
|
||||
findPreference<Preference>("hidden_features")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val link = ("<a href=\"https://developer.android.com/reference/android/content/Context#createDeviceProtectedStorageContext()\">"
|
||||
+ getString(R.string.hidden_features_text) + "</a>")
|
||||
val message = requireContext().getString(R.string.hidden_features_message, link)
|
||||
val dialogMessage = SpannableStringUtils.fromHtml(message)
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_settings_about_hidden_features)
|
||||
.setTitle(R.string.hidden_features_title)
|
||||
.setMessage(dialogMessage)
|
||||
.setPositiveButton(R.string.dialog_close, null)
|
||||
.create()
|
||||
builder.show()
|
||||
(builder.findViewById<View>(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupVersionPref() {
|
||||
val versionPreference = findPreference<Preference>("version") ?: return
|
||||
versionPreference.summary = BuildConfig.VERSION_NAME
|
||||
if (BuildConfig.DEBUG) return
|
||||
var count = 0
|
||||
versionPreference.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (sharedPreferences.getBoolean(DebugSettings.PREF_SHOW_DEBUG_SETTINGS, false))
|
||||
return@OnPreferenceClickListener true
|
||||
count++
|
||||
if (count < 5) return@OnPreferenceClickListener true
|
||||
sharedPreferences.edit().putBoolean(DebugSettings.PREF_SHOW_DEBUG_SETTINGS, true).apply()
|
||||
Toast.makeText(requireContext(), R.string.prefs_debug_settings_enabled, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,642 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import helium314.keyboard.latin.utils.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.serialization.json.Json
|
||||
import helium314.keyboard.dictionarypack.DictionaryPackConstants
|
||||
import helium314.keyboard.keyboard.KeyboardActionListener
|
||||
import helium314.keyboard.latin.utils.ChecksumCalculator
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMBER
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMPAD
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMPAD_LANDSCAPE
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_PHONE
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_PHONE_SYMBOLS
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_ARABIC
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_SHIFTED
|
||||
import helium314.keyboard.latin.AudioAndHapticFeedbackManager
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.SystemBroadcastReceiver
|
||||
import helium314.keyboard.latin.checkVersionUpgrade
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||
import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy
|
||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils.USER_DICTIONARY_SUFFIX
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.JniUtils
|
||||
import helium314.keyboard.latin.utils.ResourceUtils
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional
|
||||
import helium314.keyboard.latin.utils.infoDialog
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@Suppress("KotlinConstantConditions") // build type might be a constant, but depends on... build type!
|
||||
class AdvancedSettingsFragment : SubScreenFragment() {
|
||||
private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) }
|
||||
private val backupFilePatterns by lazy { listOf(
|
||||
"blacklists/.*\\.txt".toRegex(),
|
||||
// "layouts/$CUSTOM_LAYOUT_PREFIX+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups
|
||||
"dicts/.*/.*user\\.dict".toRegex(),
|
||||
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
|
||||
"custom_background_image.*".toRegex(),
|
||||
"custom_font".toRegex(),
|
||||
) }
|
||||
|
||||
// is there any way to get additional information into the ActivityResult? would remove the need for 5 times the (almost) same code
|
||||
private val libraryFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
copyLibrary(uri)
|
||||
}
|
||||
|
||||
private val backupFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
backup(uri)
|
||||
}
|
||||
|
||||
private val restoreFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
restore(uri)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun setupPreferences() {
|
||||
addPreferencesFromResource(R.xml.prefs_screen_advanced)
|
||||
setDebugPrefVisibility()
|
||||
val context = requireContext()
|
||||
|
||||
// When we are called from the Settings application but we are not already running, some
|
||||
// singleton and utility classes may not have been initialized. We have to call
|
||||
// initialization method of these classes here. See {@link LatinIME#onCreate()}.
|
||||
AudioAndHapticFeedbackManager.init(context)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON)
|
||||
}
|
||||
if (BuildConfig.BUILD_TYPE == "nouserlib") {
|
||||
removePreference("load_gesture_library")
|
||||
}
|
||||
setupKeyLongpressTimeoutSettings()
|
||||
setupEmojiSdkSetting()
|
||||
setupLanguageSwipeDistanceSettings()
|
||||
updateLangSwipeDistanceVisibility(sharedPreferences)
|
||||
findPreference<Preference>("load_gesture_library")?.setOnPreferenceClickListener { onClickLoadLibrary() }
|
||||
findPreference<Preference>("backup_restore")?.setOnPreferenceClickListener { showBackupRestoreDialog() }
|
||||
|
||||
findPreference<Preference>("custom_symbols_number_layouts")?.setOnPreferenceClickListener {
|
||||
showCustomizeSymbolNumberLayoutsDialog()
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("custom_functional_key_layouts")?.setOnPreferenceClickListener {
|
||||
// showCustomizeFunctionalKeyLayoutsDialog()
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>(Settings.PREF_CUSTOM_CURRENCY_KEY)?.setOnPreferenceClickListener {
|
||||
customCurrencyDialog()
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("switch_after")?.setOnPreferenceClickListener {
|
||||
switchToMainDialog()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// Remove debug preference. This is already done in onCreate, but if we come back from
|
||||
// debug prefs and have just disabled debug settings, they should disappear.
|
||||
setDebugPrefVisibility()
|
||||
}
|
||||
|
||||
private fun setDebugPrefVisibility() {
|
||||
if (!BuildConfig.DEBUG && !sharedPreferences.getBoolean(DebugSettings.PREF_SHOW_DEBUG_SETTINGS, false)) {
|
||||
removePreference(Settings.SCREEN_DEBUG)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCustomizeSymbolNumberLayoutsDialog() {
|
||||
/* val layoutNames = RawKeyboardParser.symbolAndNumberLayouts.map { it.getStringResourceOrName("layout_", requireContext()) }.toTypedArray()
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.customize_symbols_number_layouts)
|
||||
.setItems(layoutNames) { di, i ->
|
||||
di.dismiss()
|
||||
customizeSymbolNumberLayout(RawKeyboardParser.symbolAndNumberLayouts[i])
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
*/ }
|
||||
/*
|
||||
private fun customizeSymbolNumberLayout(layoutName: String) {
|
||||
val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name }
|
||||
.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") }
|
||||
val originalLayout = if (customLayoutName != null) null
|
||||
else {
|
||||
requireContext().assets.list("layouts")?.firstOrNull { it.startsWith("$layoutName.") }
|
||||
?.let { requireContext().assets.open("layouts" + File.separator + it).reader().readText() }
|
||||
}
|
||||
val displayName = layoutName.getStringResourceOrName("layout_", requireContext())
|
||||
editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layoutName.", requireContext(), originalLayout, displayName)
|
||||
}
|
||||
|
||||
private fun showCustomizeFunctionalKeyLayoutsDialog() {
|
||||
val list = listOf(CUSTOM_FUNCTIONAL_LAYOUT_NORMAL, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED)
|
||||
.map { it.substringBeforeLast(".") }
|
||||
val layoutNames = list.map { it.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) }.toTypedArray()
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.customize_functional_key_layouts)
|
||||
.setItems(layoutNames) { di, i ->
|
||||
di.dismiss()
|
||||
customizeFunctionalKeysLayout(list[i])
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun customizeFunctionalKeysLayout(layoutName: String) {
|
||||
val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name }
|
||||
.firstOrNull { it.startsWith("$layoutName.") }
|
||||
val originalLayout = if (customLayoutName != null) null
|
||||
else {
|
||||
val defaultLayoutName = if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json"
|
||||
requireContext().assets.open("layouts" + File.separator + defaultLayoutName).reader().readText()
|
||||
}
|
||||
val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext())
|
||||
editCustomLayout(customLayoutName ?: "$layoutName.", requireContext(), originalLayout, displayName)
|
||||
}
|
||||
*/
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun onClickLoadLibrary(): Boolean {
|
||||
// get architecture for telling user which file to use
|
||||
val abi = Build.SUPPORTED_ABIS[0]
|
||||
// show delete / add dialog
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.load_gesture_library)
|
||||
.setMessage(requireContext().getString(R.string.load_gesture_library_message, abi))
|
||||
.setPositiveButton(R.string.load_gesture_library_button_load) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
libraryFilePicker.launch(intent)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (libfile.exists()) {
|
||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||
libfile.delete()
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().remove(Settings.PREF_LIBRARY_CHECKSUM).commit()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun copyLibrary(uri: Uri) {
|
||||
val tmpfile = File(requireContext().filesDir.absolutePath + File.separator + "tmplib")
|
||||
try {
|
||||
val otherTemporaryFile = File(requireContext().filesDir.absolutePath + File.separator + "tmpfile")
|
||||
FileUtils.copyContentUriToNewFile(uri, requireContext(), otherTemporaryFile)
|
||||
val inputStream = FileInputStream(otherTemporaryFile)
|
||||
val outputStream = FileOutputStream(tmpfile)
|
||||
outputStream.use {
|
||||
tmpfile.setReadOnly() // as per recommendations in https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
|
||||
FileUtils.copyStreamToOtherStream(inputStream, it)
|
||||
}
|
||||
otherTemporaryFile.delete()
|
||||
|
||||
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
|
||||
if (checksum == JniUtils.expectedDefaultChecksum()) {
|
||||
renameToLibfileAndRestart(tmpfile, checksum)
|
||||
} else {
|
||||
val abi = Build.SUPPORTED_ABIS[0]
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(getString(R.string.checksum_mismatch_message, abi))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> renameToLibfileAndRestart(tmpfile, checksum) }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> tmpfile.delete() }
|
||||
.show()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
tmpfile.delete()
|
||||
// should inform user, but probably the issues will only come when reading the library
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun renameToLibfileAndRestart(file: File, checksum: String) {
|
||||
libfile.delete()
|
||||
// store checksum in default preferences (soo JniUtils)
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit().putString(Settings.PREF_LIBRARY_CHECKSUM, checksum).commit()
|
||||
file.renameTo(libfile)
|
||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
||||
}
|
||||
|
||||
private fun showBackupRestoreDialog(): Boolean {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.backup_restore_title)
|
||||
.setMessage(R.string.backup_restore_message)
|
||||
.setNegativeButton(R.string.button_backup) { _, _ ->
|
||||
val currentDate = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Calendar.getInstance().time)
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
requireContext().getString(R.string.english_ime_name)
|
||||
.replace(" ", "_") + "_backup_$currentDate.zip"
|
||||
)
|
||||
.setType("application/zip")
|
||||
backupFilePicker.launch(intent)
|
||||
}
|
||||
.setPositiveButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.button_restore) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/zip")
|
||||
restoreFilePicker.launch(intent)
|
||||
}
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun backup(uri: Uri) {
|
||||
// zip all files matching the backup patterns
|
||||
// essentially this is the typed words information, and user-added dictionaries
|
||||
val filesDir = requireContext().filesDir ?: return
|
||||
val filesPath = filesDir.path + File.separator
|
||||
val files = mutableListOf<File>()
|
||||
filesDir.walk().forEach { file ->
|
||||
val path = file.path.replace(filesPath, "")
|
||||
if (backupFilePatterns.any { path.matches(it) })
|
||||
files.add(file)
|
||||
}
|
||||
val protectedFilesDir = DeviceProtectedUtils.getFilesDir(requireContext())
|
||||
val protectedFilesPath = protectedFilesDir.path + File.separator
|
||||
val protectedFiles = mutableListOf<File>()
|
||||
protectedFilesDir.walk().forEach { file ->
|
||||
val path = file.path.replace(protectedFilesPath, "")
|
||||
if (backupFilePatterns.any { path.matches(it) })
|
||||
protectedFiles.add(file)
|
||||
}
|
||||
var error: String? = ""
|
||||
val wait = CountDownLatch(1)
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
try {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
// write files to zip
|
||||
val zipStream = ZipOutputStream(os)
|
||||
files.forEach {
|
||||
val fileStream = FileInputStream(it).buffered()
|
||||
zipStream.putNextEntry(ZipEntry(it.path.replace(filesPath, "")))
|
||||
fileStream.copyTo(zipStream, 1024)
|
||||
fileStream.close()
|
||||
zipStream.closeEntry()
|
||||
}
|
||||
protectedFiles.forEach {
|
||||
val fileStream = FileInputStream(it).buffered()
|
||||
zipStream.putNextEntry(ZipEntry(it.path.replace(protectedFilesDir.path, "unprotected")))
|
||||
fileStream.copyTo(zipStream, 1024)
|
||||
fileStream.close()
|
||||
zipStream.closeEntry()
|
||||
}
|
||||
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
||||
settingsToJsonStream(sharedPreferences.all, zipStream)
|
||||
zipStream.closeEntry()
|
||||
zipStream.putNextEntry(ZipEntry(PROTECTED_PREFS_FILE_NAME))
|
||||
settingsToJsonStream(PreferenceManager.getDefaultSharedPreferences(requireContext()).all, zipStream)
|
||||
zipStream.closeEntry()
|
||||
zipStream.close()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
error = t.message
|
||||
Log.w(TAG, "error during backup", t)
|
||||
} finally {
|
||||
wait.countDown()
|
||||
}
|
||||
}
|
||||
wait.await()
|
||||
if (!error.isNullOrBlank()) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun restore(uri: Uri) {
|
||||
var error: String? = ""
|
||||
val wait = CountDownLatch(1)
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
try {
|
||||
activity?.contentResolver?.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zip ->
|
||||
var entry: ZipEntry? = zip.nextEntry
|
||||
val filesDir = requireContext().filesDir?.path ?: return@execute
|
||||
val deviceProtectedFilesDir = DeviceProtectedUtils.getFilesDir(requireContext()).path
|
||||
Settings.getInstance().stopListener()
|
||||
while (entry != null) {
|
||||
if (entry.name.startsWith("unprotected${File.separator}")) {
|
||||
val adjustedName = entry.name.substringAfter("unprotected${File.separator}")
|
||||
if (backupFilePatterns.any { adjustedName.matches(it) }) {
|
||||
val targetFileName = upgradeFileNames(adjustedName)
|
||||
val file = File(deviceProtectedFilesDir, targetFileName)
|
||||
FileUtils.copyStreamToNewFile(zip, file)
|
||||
}
|
||||
} else if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||
val targetFileName = upgradeFileNames(entry.name)
|
||||
val file = File(filesDir, targetFileName)
|
||||
FileUtils.copyStreamToNewFile(zip, file)
|
||||
} else if (entry.name == PREFS_FILE_NAME) {
|
||||
val prefLines = String(zip.readBytes()).split("\n")
|
||||
sharedPreferences.edit().clear().apply()
|
||||
readJsonLinesToSettings(prefLines, sharedPreferences)
|
||||
} else if (entry.name == PROTECTED_PREFS_FILE_NAME) {
|
||||
val prefLines = String(zip.readBytes()).split("\n")
|
||||
val protectedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
protectedPrefs.edit().clear().apply()
|
||||
readJsonLinesToSettings(prefLines, protectedPrefs)
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
error = t.message
|
||||
Log.w(TAG, "error during restore", t)
|
||||
} finally {
|
||||
wait.countDown()
|
||||
}
|
||||
}
|
||||
wait.await()
|
||||
if (!error.isNullOrBlank()) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, error))
|
||||
}
|
||||
checkVersionUpgrade(requireContext())
|
||||
Settings.getInstance().startListener()
|
||||
SubtypeSettings.reloadEnabledSubtypes(requireContext())
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
activity?.sendBroadcast(newDictBroadcast)
|
||||
// reload current prefs screen
|
||||
preferenceScreen.removeAll()
|
||||
setupPreferences()
|
||||
// onCustomLayoutFileListChanged()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
|
||||
// todo (later): remove this when new package name has been in use for long enough, this is only for migrating from old openboard name
|
||||
private fun upgradeFileNames(originalName: String): String {
|
||||
return when {
|
||||
originalName.endsWith(USER_DICTIONARY_SUFFIX) -> {
|
||||
// replace directory after switch to language tag
|
||||
val dirName = originalName.substringAfter(File.separator).substringBefore(File.separator)
|
||||
originalName.replace(dirName, dirName.constructLocale().toLanguageTag())
|
||||
}
|
||||
originalName.startsWith("blacklists") -> {
|
||||
// replace file name after switch to language tag
|
||||
val fileName = originalName.substringAfter("blacklists${File.separator}").substringBefore(".txt")
|
||||
originalName.replace(fileName, fileName.constructLocale().toLanguageTag())
|
||||
}
|
||||
originalName.startsWith("layouts") -> {
|
||||
// replace file name after switch to language tag, but only if it's not a layout
|
||||
val localeString = originalName.substringAfter(".").substringBefore(".")
|
||||
if (localeString in listOf(LAYOUT_SYMBOLS, LAYOUT_SYMBOLS_SHIFTED, LAYOUT_SYMBOLS_ARABIC, LAYOUT_NUMBER, LAYOUT_NUMPAD, LAYOUT_NUMPAD_LANDSCAPE, LAYOUT_PHONE, LAYOUT_PHONE_SYMBOLS))
|
||||
return originalName // it's a layout!
|
||||
val locale = localeString.constructLocale()
|
||||
if (locale.toLanguageTag() != "und")
|
||||
originalName.replace(localeString, locale.toLanguageTag())
|
||||
else
|
||||
originalName // no valid locale -> must be symbols layout, don't change
|
||||
}
|
||||
originalName.startsWith("UserHistoryDictionary") -> {
|
||||
val localeString = originalName.substringAfter(".").substringBefore(".")
|
||||
val locale = localeString.constructLocale()
|
||||
originalName.replace(localeString, locale.toLanguageTag())
|
||||
}
|
||||
else -> originalName
|
||||
}
|
||||
}
|
||||
|
||||
private fun customCurrencyDialog() {
|
||||
val layout = LinearLayout(requireContext())
|
||||
layout.orientation = LinearLayout.VERTICAL
|
||||
layout.addView(TextView(requireContext()).apply { setText(R.string.customize_currencies_detail) })
|
||||
val et = EditText(requireContext()).apply { setText(sharedPreferences.getString(Settings.PREF_CUSTOM_CURRENCY_KEY, "")) }
|
||||
layout.addView(et)
|
||||
val padding = ResourceUtils.toPx(8, resources)
|
||||
layout.setPadding(3 * padding, padding, padding, padding)
|
||||
val d = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.customize_currencies)
|
||||
.setView(layout)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
sharedPreferences.edit { putString(Settings.PREF_CUSTOM_CURRENCY_KEY, et.text.toString()) }
|
||||
KeyboardLayoutSet.onSystemLocaleChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.button_default) { _, _ -> sharedPreferences.edit { putString(Settings.PREF_CUSTOM_CURRENCY_KEY, "") } }
|
||||
.create()
|
||||
et.doAfterTextChanged { d.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = et.text.toString().splitOnWhitespace().none { it.length > 8 } }
|
||||
d.show()
|
||||
}
|
||||
|
||||
private fun switchToMainDialog() {
|
||||
val checked = booleanArrayOf(
|
||||
sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, true),
|
||||
sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_EMOJI, false),
|
||||
sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_CLIP, false),
|
||||
)
|
||||
val titles = arrayOf(
|
||||
requireContext().getString(R.string.after_symbol_and_space),
|
||||
requireContext().getString(R.string.after_emoji),
|
||||
requireContext().getString(R.string.after_clip),
|
||||
)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.switch_keyboard_after)
|
||||
.setMultiChoiceItems(titles, checked) { _, i, b -> checked[i] = b }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
sharedPreferences.edit {
|
||||
putBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, checked[0])
|
||||
putBoolean(Settings.PREF_ABC_AFTER_EMOJI, checked[1])
|
||||
putBoolean(Settings.PREF_ABC_AFTER_CLIP, checked[2])
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupKeyLongpressTimeoutSettings() {
|
||||
val prefs = sharedPreferences
|
||||
findPreference<SeekBarDialogPreference>(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy {
|
||||
override fun writeValue(value: Int, key: String) = prefs.edit().putInt(key, value).apply()
|
||||
|
||||
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
|
||||
|
||||
override fun readValue(key: String) = prefs.getInt(Settings.PREF_KEY_LONGPRESS_TIMEOUT, Defaults.PREF_KEY_LONGPRESS_TIMEOUT)
|
||||
|
||||
override fun readDefaultValue(key: String) = 300
|
||||
|
||||
override fun getValueText(value: Int) =
|
||||
resources.getString(R.string.abbreviation_unit_milliseconds, value.toString())
|
||||
|
||||
override fun feedbackValue(value: Int) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupEmojiSdkSetting() {
|
||||
val prefs = sharedPreferences
|
||||
findPreference<SeekBarDialogPreference>(Settings.PREF_EMOJI_MAX_SDK)?.setInterface(object : ValueProxy {
|
||||
override fun writeValue(value: Int, key: String) = prefs.edit().putInt(key, value).apply()
|
||||
|
||||
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
|
||||
|
||||
override fun readValue(key: String) = prefs.getInt(Settings.PREF_EMOJI_MAX_SDK, Build.VERSION.SDK_INT)
|
||||
|
||||
override fun readDefaultValue(key: String) = Build.VERSION.SDK_INT
|
||||
|
||||
override fun getValueText(value: Int) = "Android " + when(value) {
|
||||
21 -> "5.0"
|
||||
22 -> "5.1"
|
||||
23 -> "6"
|
||||
24 -> "7.0"
|
||||
25 -> "7.1"
|
||||
26 -> "8.0"
|
||||
27 -> "8.1"
|
||||
28 -> "9"
|
||||
29 -> "10"
|
||||
30 -> "11"
|
||||
31 -> "12"
|
||||
32 -> "12L"
|
||||
33 -> "13"
|
||||
34 -> "14"
|
||||
35 -> "15"
|
||||
else -> "version unknown"
|
||||
}
|
||||
|
||||
override fun feedbackValue(value: Int) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupLanguageSwipeDistanceSettings() {
|
||||
val prefs = sharedPreferences
|
||||
findPreference<SeekBarDialogPreference>(Settings.PREF_LANGUAGE_SWIPE_DISTANCE)?.setInterface(object : ValueProxy {
|
||||
override fun writeValue(value: Int, key: String) = prefs.edit().putInt(key, value).apply()
|
||||
|
||||
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
|
||||
|
||||
override fun readValue(key: String) = prefs.getInt(Settings.PREF_LANGUAGE_SWIPE_DISTANCE, 5)
|
||||
|
||||
override fun readDefaultValue(key: String) = 5
|
||||
|
||||
override fun getValueText(value: Int) = value.toString()
|
||||
|
||||
override fun feedbackValue(value: Int) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateLangSwipeDistanceVisibility(prefs: SharedPreferences) {
|
||||
val horizontalSpaceSwipe = Settings.readHorizontalSpaceSwipe(prefs)
|
||||
val verticalSpaceSwipe = Settings.readVerticalSpaceSwipe(prefs)
|
||||
val visibility = horizontalSpaceSwipe == KeyboardActionListener.SWIPE_SWITCH_LANGUAGE
|
||||
|| verticalSpaceSwipe == KeyboardActionListener.SWIPE_SWITCH_LANGUAGE
|
||||
setPreferenceVisible(Settings.PREF_LANGUAGE_SWIPE_DISTANCE, visibility)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
Settings.PREF_SHOW_SETUP_WIZARD_ICON -> SystemBroadcastReceiver.toggleAppIcon(requireContext())
|
||||
"more_popup_keys" -> KeyboardLayoutSet.onSystemLocaleChanged()
|
||||
Settings.PREF_SPACE_HORIZONTAL_SWIPE -> updateLangSwipeDistanceVisibility(prefs)
|
||||
Settings.PREF_SPACE_VERTICAL_SWIPE -> updateLangSwipeDistanceVisibility(prefs)
|
||||
Settings.PREF_EMOJI_MAX_SDK -> KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("UNCHECKED_CAST") // it is checked... but whatever (except string set, because can't check for that))
|
||||
private fun settingsToJsonStream(settings: Map<String?, Any?>, out: OutputStream) {
|
||||
val booleans = settings.filter { it.key is String && it.value is Boolean } as Map<String, Boolean>
|
||||
val ints = settings.filter { it.key is String && it.value is Int } as Map<String, Int>
|
||||
val longs = settings.filter { it.key is String && it.value is Long } as Map<String, Long>
|
||||
val floats = settings.filter { it.key is String && it.value is Float } as Map<String, Float>
|
||||
val strings = settings.filter { it.key is String && it.value is String } as Map<String, String>
|
||||
val stringSets = settings.filter { it.key is String && it.value is Set<*> } as Map<String, Set<String>>
|
||||
// now write
|
||||
out.write("boolean settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(booleans).toByteArray())
|
||||
out.write("\nint settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(ints).toByteArray())
|
||||
out.write("\nlong settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(longs).toByteArray())
|
||||
out.write("\nfloat settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(floats).toByteArray())
|
||||
out.write("\nstring settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(strings).toByteArray())
|
||||
out.write("\nstring set settings\n".toByteArray())
|
||||
out.write(Json.encodeToString(stringSets).toByteArray())
|
||||
}
|
||||
|
||||
private fun readJsonLinesToSettings(list: List<String>, prefs: SharedPreferences): Boolean {
|
||||
val i = list.iterator()
|
||||
val e = prefs.edit()
|
||||
try {
|
||||
while (i.hasNext()) {
|
||||
when (i.next()) {
|
||||
"boolean settings" -> Json.decodeFromString<Map<String, Boolean>>(i.next()).forEach { e.putBoolean(it.key, it.value) }
|
||||
"int settings" -> Json.decodeFromString<Map<String, Int>>(i.next()).forEach { e.putInt(it.key, it.value) }
|
||||
"long settings" -> Json.decodeFromString<Map<String, Long>>(i.next()).forEach { e.putLong(it.key, it.value) }
|
||||
"float settings" -> Json.decodeFromString<Map<String, Float>>(i.next()).forEach { e.putFloat(it.key, it.value) }
|
||||
"string settings" -> Json.decodeFromString<Map<String, String>>(i.next()).forEach { e.putString(it.key, it.value) }
|
||||
"string set settings" -> Json.decodeFromString<Map<String, Set<String>>>(i.next()).forEach { e.putStringSet(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
e.apply()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val PREFS_FILE_NAME = "preferences.json"
|
||||
private const val PROTECTED_PREFS_FILE_NAME = "protected_preferences.json"
|
||||
private const val TAG = "AdvancedSettingsFragment"
|
|
@ -1,476 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.util.TypedValueCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.TwoStatePreference
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.KeyboardTheme
|
||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.databinding.ReorderDialogItemBinding
|
||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
||||
import helium314.keyboard.latin.utils.ResourceUtils
|
||||
import helium314.keyboard.latin.utils.confirmDialog
|
||||
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||
import helium314.keyboard.latin.utils.infoDialog
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.lang.Float.max
|
||||
import java.lang.Float.min
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* "Appearance" settings sub screen.
|
||||
*/
|
||||
class AppearanceSettingsFragment : SubScreenFragment() {
|
||||
private var needsReload = false
|
||||
|
||||
private val stylePref: ListPreference by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_STYLE)!! }
|
||||
private val iconStylePref: ListPreference by lazy { preferenceScreen.findPreference(Settings.PREF_ICON_STYLE)!! }
|
||||
private val colorsPref: ListPreference by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_COLORS)!! }
|
||||
private val colorsNightPref: ListPreference? by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_COLORS_NIGHT) }
|
||||
private val dayNightPref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_DAY_NIGHT) }
|
||||
private val userColorsPref: Preference by lazy { preferenceScreen.findPreference("theme_select_colors")!! }
|
||||
private val userColorsPrefNight: Preference? by lazy { preferenceScreen.findPreference("theme_select_colors_night") }
|
||||
private val splitLandscapePref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD_LANDSCAPE) }
|
||||
private val splitPortraitPref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD) }
|
||||
private val splitScalePref: Preference? by lazy { preferenceScreen.findPreference(Settings.PREF_SPLIT_SPACER_SCALE) }
|
||||
private val splitScaleLandscapePref: Preference? by lazy { preferenceScreen.findPreference(Settings.PREF_SPLIT_SPACER_SCALE_LANDSCAPE) }
|
||||
|
||||
private val dayImageFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
loadImage(uri, false, false)
|
||||
}
|
||||
|
||||
private val nightImageFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
loadImage(uri, true, false)
|
||||
}
|
||||
|
||||
private val dayImageFilePickerLandscape = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
loadImage(uri, false, true)
|
||||
}
|
||||
|
||||
private val nightImageFilePickerLandscape = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
loadImage(uri, true, true)
|
||||
}
|
||||
|
||||
private val fontFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
saveCustomTypeface(uri)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.prefs_screen_appearance)
|
||||
|
||||
removeUnsuitablePreferences()
|
||||
setupTheme()
|
||||
setColorPrefs(sharedPreferences.getString(Settings.PREF_THEME_STYLE, KeyboardTheme.STYLE_MATERIAL)!!)
|
||||
|
||||
setupScalePrefs(Settings.PREF_KEYBOARD_HEIGHT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
setupScalePrefs(Settings.PREF_BOTTOM_PADDING_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
setupScalePrefs(Settings.PREF_BOTTOM_PADDING_SCALE_LANDSCAPE, 0f)
|
||||
setupScalePrefs(Settings.PREF_FONT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
setupScalePrefs(Settings.PREF_EMOJI_FONT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
setupScalePrefs(Settings.PREF_SIDE_PADDING_SCALE, 0f)
|
||||
setupScalePrefs(Settings.PREF_SIDE_PADDING_SCALE_LANDSCAPE, 0f)
|
||||
if (splitScalePref != null) {
|
||||
setupScalePrefs(Settings.PREF_SPLIT_SPACER_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
splitScalePref?.isVisible = splitPortraitPref?.isChecked == true
|
||||
splitPortraitPref?.setOnPreferenceChangeListener { _, value ->
|
||||
splitScalePref?.isVisible = value as Boolean
|
||||
true
|
||||
}
|
||||
}
|
||||
if (splitScaleLandscapePref != null) {
|
||||
setupScalePrefs(Settings.PREF_SPLIT_SPACER_SCALE_LANDSCAPE, SettingsValues.DEFAULT_SIZE_SCALE)
|
||||
splitScaleLandscapePref?.isVisible = splitLandscapePref?.isChecked == true
|
||||
splitLandscapePref?.setOnPreferenceChangeListener { _, value ->
|
||||
splitScaleLandscapePref?.isVisible = value as Boolean
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("custom_background_image")?.setOnPreferenceClickListener { onClickLoadImage(false) }
|
||||
findPreference<Preference>("custom_background_image_landscape")?.setOnPreferenceClickListener { onClickLoadImage(true) }
|
||||
findPreference<Preference>("custom_font")?.setOnPreferenceClickListener { onClickCustomFont() }
|
||||
findPreference<Preference>(Settings.PREF_CUSTOM_ICON_NAMES)?.setOnPreferenceClickListener {
|
||||
if (needsReload)
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
onClickCustomizeIcons()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (needsReload)
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
needsReload = false
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
||||
super.onSharedPreferenceChanged(prefs, key)
|
||||
needsReload = true // may not always necessary, but that's ok
|
||||
}
|
||||
|
||||
private fun removeUnsuitablePreferences() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
removePreference(Settings.PREF_THEME_DAY_NIGHT)
|
||||
removePreference(Settings.PREF_THEME_COLORS_NIGHT)
|
||||
} else {
|
||||
// on P there is experimental support for night mode, exposed by some roms like LineageOS
|
||||
// try to detect this using UI_MODE_NIGHT_UNDEFINED, but actually the system could always report day too?
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
|
||||
&& (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_UNDEFINED
|
||||
) {
|
||||
removePreference(Settings.PREF_THEME_DAY_NIGHT)
|
||||
removePreference(Settings.PREF_THEME_COLORS_NIGHT)
|
||||
removePreference("theme_select_colors_night")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setColorPrefs(style: String) {
|
||||
/* colorsPref.apply {
|
||||
entryValues = if (style == KeyboardTheme.STYLE_HOLO) KeyboardTheme.COLORS.toTypedArray()
|
||||
else KeyboardTheme.COLORS.filterNot { it == KeyboardTheme.THEME_HOLO_WHITE }.toTypedArray()
|
||||
entries = entryValues.getNamesFromResourcesIfAvailable("theme_name_")
|
||||
if (value !in entryValues)
|
||||
value = entryValues.first().toString()
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
userColorsPref.isVisible = value == KeyboardTheme.THEME_USER
|
||||
true
|
||||
}
|
||||
}
|
||||
colorsNightPref?.apply {
|
||||
entryValues = if (style == KeyboardTheme.STYLE_HOLO) KeyboardTheme.COLORS_DARK.toTypedArray()
|
||||
else KeyboardTheme.COLORS_DARK.filterNot { it == KeyboardTheme.THEME_HOLO_WHITE }.toTypedArray()
|
||||
entries = entryValues.getNamesFromResourcesIfAvailable("theme_name_")
|
||||
if (value !in entryValues)
|
||||
value = entryValues.first().toString()
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
userColorsPrefNight?.isVisible = value == KeyboardTheme.THEME_USER_NIGHT
|
||||
true
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private fun setupTheme() {
|
||||
/* stylePref.apply {
|
||||
entryValues = KeyboardTheme.STYLES
|
||||
entries = entryValues.getNamesFromResourcesIfAvailable("style_name_")
|
||||
if (value !in entryValues)
|
||||
value = entryValues.first().toString()
|
||||
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
setColorPrefs(value.toString())
|
||||
true
|
||||
}
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
}
|
||||
iconStylePref.apply {
|
||||
entryValues = KeyboardTheme.STYLES
|
||||
entries = entryValues.getNamesFromResourcesIfAvailable("style_name_")
|
||||
if (value !in entryValues)
|
||||
value = entryValues.first().toString()
|
||||
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
true
|
||||
}
|
||||
summary = entries[entryValues.indexOfFirst { it == value }]
|
||||
}
|
||||
dayNightPref?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
|
||||
val yesThisIsBoolean = value as Boolean // apparently kotlin smartcast got less smart with 2.0.0
|
||||
colorsNightPref?.isVisible = yesThisIsBoolean
|
||||
userColorsPrefNight?.isVisible = yesThisIsBoolean && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT
|
||||
true
|
||||
}
|
||||
colorsNightPref?.isVisible = dayNightPref?.isChecked == true
|
||||
userColorsPref.isVisible = colorsPref.value == KeyboardTheme.THEME_USER
|
||||
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT*/
|
||||
}
|
||||
|
||||
// performance is not good, but not bad enough to justify work
|
||||
private fun onClickCustomizeIcons(): Boolean {
|
||||
val ctx = requireContext()
|
||||
val padding = ResourceUtils.toPx(8, ctx.resources)
|
||||
val ll = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(padding, 3 * padding, padding, padding)
|
||||
}
|
||||
val builder = AlertDialog.Builder(ctx)
|
||||
.setTitle(R.string.customize_icons)
|
||||
.setView(ScrollView(context).apply { addView(ll) })
|
||||
.setPositiveButton(R.string.dialog_close, null)
|
||||
if (sharedPreferences.contains(Settings.PREF_CUSTOM_ICON_NAMES))
|
||||
builder.setNeutralButton(R.string.button_default) { _, _ ->
|
||||
confirmDialog(
|
||||
ctx,
|
||||
ctx.getString(R.string.customize_icons_reset_message),
|
||||
ctx.getString(android.R.string.ok),
|
||||
{ sharedPreferences.edit().remove(Settings.PREF_CUSTOM_ICON_NAMES).apply() }
|
||||
)
|
||||
}
|
||||
val dialog = builder.create()
|
||||
|
||||
val cf = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(ContextCompat.getColor(ctx, R.color.foreground), BlendModeCompat.SRC_IN)
|
||||
val iconsAndNames = KeyboardIconsSet.getAllIcons(ctx).keys.map { iconName ->
|
||||
val name = iconName.getStringResourceOrName("", ctx)
|
||||
if (name == iconName) iconName to iconName.getStringResourceOrName("label_", ctx).toString()
|
||||
else iconName to name.toString()
|
||||
}
|
||||
iconsAndNames.sortedBy { it.second }.forEach { (iconName, name) ->
|
||||
val b = ReorderDialogItemBinding.inflate(LayoutInflater.from(ctx), ll, true)
|
||||
b.reorderItemIcon.setImageDrawable(KeyboardIconsSet.instance.getNewDrawable(iconName, ctx))
|
||||
b.reorderItemIcon.colorFilter = cf
|
||||
b.reorderItemIcon.isVisible = true
|
||||
b.reorderItemName.text = name
|
||||
b.root.setOnClickListener {
|
||||
customizeIcon(iconName)
|
||||
dialog.dismiss()
|
||||
}
|
||||
b.reorderItemSwitch.isGone = true
|
||||
b.reorderItemDragIndicator.isGone = true
|
||||
}
|
||||
dialog.show()
|
||||
return true
|
||||
}
|
||||
|
||||
// todo: icon size is an important difference between holo and others, but really awful to work with
|
||||
// scaling the intrinsic icon width may look awful depending on display density
|
||||
private fun customizeIcon(iconName: String) {
|
||||
val ctx = requireContext()
|
||||
val rv = RecyclerView(ctx)
|
||||
rv.layoutManager = GridLayoutManager(ctx, 6)
|
||||
val padding = ResourceUtils.toPx(6, resources)
|
||||
rv.setPadding(padding, 3 * padding, padding, padding)
|
||||
val icons = KeyboardIconsSet.getAllIcons(ctx)
|
||||
val iconsList = icons[iconName].orEmpty().toSet().toMutableList()
|
||||
val iconsSet = icons.values.flatten().toMutableSet()
|
||||
iconsSet.removeAll(iconsList)
|
||||
iconsList.addAll(iconsSet)
|
||||
val foregroundColor = ContextCompat.getColor(ctx, R.color.foreground)
|
||||
val iconColorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(foregroundColor, BlendModeCompat.SRC_IN)
|
||||
|
||||
var currentIconId = KeyboardIconsSet.instance.iconIds[iconName]
|
||||
|
||||
val adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val v = ImageView(ctx)
|
||||
v.colorFilter = iconColorFilter
|
||||
v.setPadding(padding, padding, padding, padding)
|
||||
return object : RecyclerView.ViewHolder(v) { }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = iconsList.size
|
||||
|
||||
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||
val icon = ContextCompat.getDrawable(ctx, iconsList[position])?.mutate()
|
||||
val imageView = viewHolder.itemView as? ImageView
|
||||
imageView?.setImageDrawable(icon)
|
||||
if (iconsList[position] == currentIconId) imageView?.setColorFilter(R.color.accent)
|
||||
else imageView?.colorFilter = iconColorFilter
|
||||
viewHolder.itemView.setOnClickListener { v ->
|
||||
rv.forEach { (it as? ImageView)?.colorFilter = iconColorFilter }
|
||||
(v as? ImageView)?.setColorFilter(R.color.accent)
|
||||
currentIconId = iconsList[position]
|
||||
}
|
||||
}
|
||||
}
|
||||
rv.adapter = adapter
|
||||
val title = iconName.getStringResourceOrName("", ctx).takeUnless { it == iconName }
|
||||
?: iconName.getStringResourceOrName("label_", ctx)
|
||||
val builder = AlertDialog.Builder(ctx)
|
||||
.setTitle(title)
|
||||
.setView(rv)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
runCatching {
|
||||
val icons2 = customIconNames(sharedPreferences).toMutableMap()
|
||||
icons2[iconName] = currentIconId?.let { resources.getResourceEntryName(it) } ?: return@runCatching
|
||||
sharedPreferences.edit().putString(Settings.PREF_CUSTOM_ICON_NAMES, Json.encodeToString(icons2)).apply()
|
||||
KeyboardIconsSet.instance.loadIcons(ctx)
|
||||
}
|
||||
onClickCustomizeIcons()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> onClickCustomizeIcons() }
|
||||
if (customIconNames(sharedPreferences).contains(iconName))
|
||||
builder.setNeutralButton(R.string.button_default) { _, _ ->
|
||||
runCatching {
|
||||
val icons2 = customIconNames(sharedPreferences).toMutableMap()
|
||||
icons2.remove(iconName)
|
||||
if (icons2.isEmpty()) sharedPreferences.edit().remove(Settings.PREF_CUSTOM_ICON_NAMES).apply()
|
||||
else sharedPreferences.edit().putString(Settings.PREF_CUSTOM_ICON_NAMES, Json.encodeToString(icons2)).apply()
|
||||
KeyboardIconsSet.instance.loadIcons(ctx)
|
||||
}
|
||||
onClickCustomizeIcons()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun onClickLoadImage(landscape: Boolean): Boolean {
|
||||
if (sharedPreferences.getBoolean(Settings.PREF_THEME_DAY_NIGHT, Defaults.PREF_THEME_DAY_NIGHT)) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.day_or_night_image)
|
||||
.setPositiveButton(R.string.day_or_night_day) { _, _ -> customImageDialog(false, landscape) }
|
||||
.setNegativeButton(R.string.day_or_night_night) { _, _ -> customImageDialog(true, landscape) }
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
customImageDialog(false, landscape)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun customImageDialog(night: Boolean, landscape: Boolean) {
|
||||
val imageFile = Settings.getCustomBackgroundFile(requireContext(), night, landscape)
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
.setMessage(if (landscape) R.string.customize_background_image_landscape else R.string.customize_background_image)
|
||||
.setPositiveButton(R.string.button_load_custom) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("image/*")
|
||||
if (landscape) {
|
||||
if (night) nightImageFilePickerLandscape.launch(intent)
|
||||
else dayImageFilePickerLandscape.launch(intent)
|
||||
} else {
|
||||
if (night) nightImageFilePicker.launch(intent)
|
||||
else dayImageFilePicker.launch(intent)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (imageFile.exists()) {
|
||||
builder.setNeutralButton(R.string.delete) { _, _ ->
|
||||
imageFile.delete()
|
||||
Settings.clearCachedBackgroundImages()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun loadImage(uri: Uri, night: Boolean, landscape: Boolean) {
|
||||
val imageFile = Settings.getCustomBackgroundFile(requireContext(), night, landscape)
|
||||
FileUtils.copyContentUriToNewFile(uri, requireContext(), imageFile)
|
||||
try {
|
||||
BitmapFactory.decodeFile(imageFile.absolutePath)
|
||||
} catch (_: Exception) {
|
||||
infoDialog(requireContext(), R.string.file_read_error)
|
||||
imageFile.delete()
|
||||
}
|
||||
Settings.clearCachedBackgroundImages()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
|
||||
private fun onClickCustomFont(): Boolean {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("*/*")
|
||||
val fontFile = Settings.getCustomFontFile(requireContext())
|
||||
if (fontFile.exists()) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.custom_font)
|
||||
.setPositiveButton(R.string.load) { _, _ -> fontFilePicker.launch(intent) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.delete) { _, _ ->
|
||||
fontFile.delete()
|
||||
Settings.clearCachedTypeface()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
fontFilePicker.launch(intent)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun saveCustomTypeface(uri: Uri) {
|
||||
val fontFile = Settings.getCustomFontFile(requireContext())
|
||||
val tempFile = File(DeviceProtectedUtils.getFilesDir(context), "temp_file")
|
||||
FileUtils.copyContentUriToNewFile(uri, requireContext(), tempFile)
|
||||
try {
|
||||
val typeface = Typeface.createFromFile(tempFile)
|
||||
fontFile.delete()
|
||||
tempFile.renameTo(fontFile)
|
||||
Settings.clearCachedTypeface()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
} catch (_: Exception) {
|
||||
infoDialog(requireContext(), R.string.file_read_error)
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupScalePrefs(prefKey: String, defaultValue: Float) {
|
||||
val prefs = sharedPreferences
|
||||
val pref = findPreference(prefKey) as? SeekBarDialogPreference
|
||||
pref?.setInterface(object : SeekBarDialogPreference.ValueProxy {
|
||||
|
||||
private fun getValueFromPercentage(percentage: Int) = percentage / PERCENTAGE_FLOAT
|
||||
|
||||
private fun getPercentageFromValue(floatValue: Float) = (floatValue * PERCENTAGE_FLOAT).toInt()
|
||||
|
||||
override fun writeValue(value: Int, key: String) = prefs.edit().putFloat(key, getValueFromPercentage(value)).apply()
|
||||
|
||||
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
|
||||
|
||||
override fun readValue(key: String) = getPercentageFromValue(prefs.getFloat(prefKey, defaultValue))
|
||||
|
||||
override fun readDefaultValue(key: String) = getPercentageFromValue(defaultValue)
|
||||
|
||||
override fun getValueText(value: Int) = String.format(Locale.ROOT, "%d%%", value)
|
||||
|
||||
override fun feedbackValue(value: Int) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
private fun Array<CharSequence>.getNamesFromResourcesIfAvailable(prefix: String) =
|
||||
map { it.getStringResourceOrName(prefix, requireContext()) }.toTypedArray()
|
||||
|
||||
companion object {
|
||||
private const val PERCENTAGE_FLOAT = 100.0f
|
||||
}
|
||||
}
|
|
@ -1,440 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.rarepebble.colorpicker.ColorPickerView
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.KeyboardTheme
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.RichInputMethodManager
|
||||
import helium314.keyboard.latin.common.ColorType
|
||||
import helium314.keyboard.latin.common.default
|
||||
import helium314.keyboard.latin.databinding.ColorSettingBinding
|
||||
import helium314.keyboard.latin.databinding.ColorSettingsBinding
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.ResourceUtils
|
||||
import helium314.keyboard.latin.utils.infoDialog
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.EnumMap
|
||||
|
||||
open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvider {
|
||||
/*
|
||||
private val binding by viewBinding(ColorSettingsBinding::bind)
|
||||
open val isNight = false
|
||||
open val titleResId = R.string.select_user_colors
|
||||
|
||||
// 0 for default
|
||||
// 1 for more colors
|
||||
// 2 for all colors
|
||||
private var moreColors: Int
|
||||
get() = prefs.getInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, isNight), 0)
|
||||
set(value) { prefs.edit().putInt(Settings.getColorPref(Settings.PREF_SHOW_MORE_COLORS, isNight), value).apply() }
|
||||
|
||||
private val prefs by lazy { requireContext().prefs() }
|
||||
|
||||
private val colorPrefsAndNames by lazy {
|
||||
listOf(
|
||||
Settings.PREF_COLOR_BACKGROUND_SUFFIX to R.string.select_color_background,
|
||||
Settings.PREF_COLOR_KEYS_SUFFIX to R.string.select_color_key_background,
|
||||
Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX to R.string.select_color_functional_key_background,
|
||||
Settings.PREF_COLOR_SPACEBAR_SUFFIX to R.string.select_color_spacebar_background,
|
||||
Settings.PREF_COLOR_TEXT_SUFFIX to R.string.select_color_key,
|
||||
Settings.PREF_COLOR_HINT_TEXT_SUFFIX to R.string.select_color_key_hint,
|
||||
Settings.PREF_COLOR_SUGGESTION_TEXT_SUFFIX to R.string.select_color_suggestion,
|
||||
Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX to R.string.select_color_spacebar_text,
|
||||
Settings.PREF_COLOR_ACCENT_SUFFIX to R.string.select_color_accent,
|
||||
Settings.PREF_COLOR_GESTURE_SUFFIX to R.string.select_color_gesture,
|
||||
).map { it.first to requireContext().getString(it.second) }
|
||||
}
|
||||
|
||||
private val colorPrefsToHideInitially by lazy {
|
||||
listOf(Settings.PREF_COLOR_SUGGESTION_TEXT_SUFFIX,Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, Settings.PREF_COLOR_GESTURE_SUFFIX) +
|
||||
if (prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false)) listOf(Settings.PREF_COLOR_SPACEBAR_SUFFIX)
|
||||
else listOf(Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isNight != ResourceUtils.isNight(requireContext().resources)) {
|
||||
// reload to get the right configuration
|
||||
forceOppositeTheme = true
|
||||
reloadKeyboard(false)
|
||||
}
|
||||
val activity = activity
|
||||
if (activity is AppCompatActivity) {
|
||||
val actionBar = activity.supportActionBar ?: return
|
||||
actionBar.setTitle(titleResId)
|
||||
}
|
||||
activity?.addMenuProvider(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
forceOppositeTheme = false
|
||||
if (isNight != ResourceUtils.isNight(requireContext().resources))
|
||||
// reload again so the correct configuration is applied
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
activity?.removeMenuProvider(this)
|
||||
}
|
||||
*/
|
||||
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu.add(Menu.NONE, 0, Menu.NONE, R.string.main_colors)
|
||||
menu.add(Menu.NONE, 1, Menu.NONE, R.string.more_colors)
|
||||
menu.add(Menu.NONE, 2, Menu.NONE, R.string.all_colors)
|
||||
menu.add(Menu.NONE, 3, Menu.NONE, R.string.save)
|
||||
menu.add(Menu.NONE, 4, Menu.NONE, R.string.load)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
// necessary, even though we only have a single menu item
|
||||
// because the back arrow on top absurdly is implemented as a menu item
|
||||
/* if (menuItem.itemId in 0..2) {
|
||||
if (moreColors == menuItem.itemId) return true
|
||||
if (moreColors == 2 || menuItem.itemId == 2) {
|
||||
RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
|
||||
reloadKeyboard(false)
|
||||
}
|
||||
moreColors = menuItem.itemId
|
||||
updateColorPrefs()
|
||||
return true
|
||||
}
|
||||
if (menuItem.itemId == 3) {
|
||||
saveDialog()
|
||||
return true
|
||||
}
|
||||
if (menuItem.itemId == 4) {
|
||||
loadDialog()
|
||||
return true
|
||||
}*/
|
||||
return false
|
||||
}
|
||||
/*
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
// updateColorPrefs must be called after super.onViewStateRestored because for some reason Android
|
||||
// decides to set the checked state of the bottom-most switch to ALL switches during "restore"
|
||||
updateColorPrefs()
|
||||
}
|
||||
|
||||
private fun updateColorPrefs() {
|
||||
binding.colorSettingsContainer.removeAllViews()
|
||||
if (moreColors == 2) showAllColors()
|
||||
else showMainColors()
|
||||
}
|
||||
|
||||
private fun showAllColors() {
|
||||
binding.info.isVisible = true
|
||||
val colors = readAllColorsMap(prefs, isNight)
|
||||
ColorType.entries.forEach { type ->
|
||||
val color = colors[type] ?: type.default()
|
||||
|
||||
val csb = ColorSettingBinding.inflate(layoutInflater, binding.colorSettingsContainer, true)
|
||||
csb.root.tag = type
|
||||
csb.colorSwitch.isGone = true
|
||||
csb.colorPreview.setColorFilter(color)
|
||||
csb.colorText.text = type.name
|
||||
|
||||
val clickListener = View.OnClickListener {
|
||||
val hidden = RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
|
||||
val picker = ColorPickerView(requireContext())
|
||||
picker.showAlpha(type != ColorType.MAIN_BACKGROUND) // background behind background looks broken and sometimes is dark, sometimes light
|
||||
picker.showHex(true)
|
||||
picker.showPreview(true)
|
||||
picker.color = color
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
builder
|
||||
.setTitle(type.name)
|
||||
.setView(picker)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val colorMap = readAllColorsMap(prefs, isNight) // better re-read it
|
||||
colorMap[type] = picker.color
|
||||
writeAllColorsMap(colorMap, prefs, isNight)
|
||||
updateAllColorPreviews()
|
||||
reloadKeyboard(hidden)
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
// Reduce the size of the dialog in portrait mode
|
||||
val wrapContent = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
val widthPortrait = (resources.displayMetrics.widthPixels * 0.80f).toInt()
|
||||
val orientation = (resources.configuration.orientation)
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
dialog.window?.setLayout(wrapContent, wrapContent)
|
||||
else
|
||||
dialog.window?.setLayout(widthPortrait, wrapContent)
|
||||
}
|
||||
csb.colorTextContainer.setOnClickListener(clickListener)
|
||||
csb.colorPreview.setOnClickListener(clickListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMainColors() {
|
||||
binding.info.isGone = true
|
||||
val prefPrefix = if (isNight) Settings.PREF_THEME_USER_COLOR_NIGHT_PREFIX else Settings.PREF_THEME_USER_COLOR_PREFIX
|
||||
colorPrefsAndNames.forEachIndexed { index, (colorPref, colorPrefName) ->
|
||||
val autoColor = prefs.getBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, true)
|
||||
if (moreColors == 0 && colorPref in colorPrefsToHideInitially && autoColor)
|
||||
return@forEachIndexed
|
||||
val csb = ColorSettingBinding.inflate(layoutInflater, binding.colorSettingsContainer, true)
|
||||
csb.root.tag = index
|
||||
csb.colorSwitch.isChecked = !autoColor
|
||||
csb.colorPreview.setColorFilter(Settings.readUserColor(prefs, requireContext(), colorPref, isNight))
|
||||
csb.colorText.text = colorPrefName
|
||||
if (!csb.colorSwitch.isChecked) {
|
||||
csb.colorSummary.setText(R.string.auto_user_color)
|
||||
}
|
||||
val switchListener = CompoundButton.OnCheckedChangeListener { _, b ->
|
||||
val hidden = RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
|
||||
prefs.edit { putBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, !b) }
|
||||
if (b) csb.colorSummary.text = ""
|
||||
else csb.colorSummary.setText(R.string.auto_user_color)
|
||||
reloadKeyboard(hidden)
|
||||
updateMainColorPreviews()
|
||||
}
|
||||
csb.colorSwitch.setOnCheckedChangeListener(switchListener)
|
||||
|
||||
val clickListener = View.OnClickListener {
|
||||
val hidden = RichInputMethodManager.getInstance().inputMethodManager.hideSoftInputFromWindow(binding.dummyText.windowToken, 0)
|
||||
val initialColor = Settings.readUserColor(prefs, requireContext(), colorPref, isNight)
|
||||
val picker = ColorPickerView(requireContext())
|
||||
picker.showAlpha(colorPref != Settings.PREF_COLOR_BACKGROUND_SUFFIX) // background behind background looks broken and sometimes is dark, sometimes light
|
||||
picker.showHex(true)
|
||||
picker.showPreview(true)
|
||||
picker.color = initialColor
|
||||
// without the observer, the color previews in the background don't update
|
||||
// but storing the pref and resetting on cancel is really bad style, so this is disabled for now
|
||||
/* picker.addColorObserver { observer ->
|
||||
prefs.edit { putInt(prefPrefix + colorPref, observer.color) }
|
||||
if (!csb.colorSwitch.isChecked) {
|
||||
prefs.edit { putBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, false) }
|
||||
csb.colorSwitch.setOnCheckedChangeListener(null)
|
||||
csb.colorSwitch.isChecked = true
|
||||
csb.colorSummary.text = ""
|
||||
csb.colorSwitch.setOnCheckedChangeListener(switchListener)
|
||||
updateColorPreviews()
|
||||
return@addColorObserver
|
||||
}
|
||||
updateColorPreviews()
|
||||
}*/
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
builder
|
||||
.setTitle(colorPrefName)
|
||||
.setView(picker)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
prefs.edit { putInt(prefPrefix + colorPref, picker.color) }
|
||||
if (!csb.colorSwitch.isChecked) {
|
||||
prefs.edit { putBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, false) }
|
||||
csb.colorSwitch.setOnCheckedChangeListener(null)
|
||||
csb.colorSwitch.isChecked = true
|
||||
csb.colorSummary.text = ""
|
||||
csb.colorSwitch.setOnCheckedChangeListener(switchListener)
|
||||
updateMainColorPreviews()
|
||||
} else {
|
||||
updateMainColorPreviews()
|
||||
}
|
||||
reloadKeyboard(hidden)
|
||||
}
|
||||
// The Default button appears only when a color has already been defined
|
||||
if (csb.colorSwitch.isChecked) {
|
||||
// Reset the color and the color picker to their initial state
|
||||
builder.setNeutralButton(R.string.button_default) { _, _ ->
|
||||
prefs.edit { remove(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX) }
|
||||
csb.colorSwitch.isChecked = false
|
||||
}
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
// Reduce the size of the dialog in portrait mode
|
||||
val wrapContent = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
val widthPortrait = (resources.displayMetrics.widthPixels * 0.80f).toInt()
|
||||
val orientation = (resources.configuration.orientation)
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
dialog.window?.setLayout(wrapContent, wrapContent)
|
||||
else
|
||||
dialog.window?.setLayout(widthPortrait, wrapContent)
|
||||
}
|
||||
csb.colorTextContainer.setOnClickListener(clickListener)
|
||||
csb.colorPreview.setOnClickListener(clickListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMainColorPreviews() {
|
||||
binding.colorSettingsContainer.forEach { view ->
|
||||
val index = view.tag as? Int ?: return@forEach
|
||||
val color = Settings.readUserColor(prefs, requireContext(), colorPrefsAndNames[index].first, isNight)
|
||||
view.findViewById<ImageView>(R.id.color_preview)?.setColorFilter(color)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAllColorPreviews() {
|
||||
val colorMap = readAllColorsMap(prefs, isNight)
|
||||
binding.colorSettingsContainer.forEach { view ->
|
||||
val type = view.tag as? ColorType ?: return@forEach
|
||||
val color = colorMap[type] ?: type.default()
|
||||
view.findViewById<ImageView>(R.id.color_preview)?.setColorFilter(color)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadKeyboard(show: Boolean) {
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
if (!show) return@execute
|
||||
// for some reason showing again does not work when running with executor
|
||||
// but when running without it's noticeably slow, and sometimes produces glitches
|
||||
Thread.sleep(100)
|
||||
RichInputMethodManager.getInstance().inputMethodManager.showSoftInput(binding.dummyText, 0)
|
||||
}
|
||||
}
|
||||
*/
|
||||
companion object {
|
||||
var forceOppositeTheme = false
|
||||
}
|
||||
/*
|
||||
// ----------------- stuff for import / export ---------------------------
|
||||
|
||||
private fun saveDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.save)
|
||||
.setPositiveButton(R.string.button_save_file) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(Intent.EXTRA_TITLE,"theme.json")
|
||||
.setType("application/json")
|
||||
saveFilePicker.launch(intent)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.copy_to_clipboard) { _, _ ->
|
||||
val cm = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cm.setPrimaryClip(ClipData.newPlainText("HeliBoard theme", getColorString()))
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun loadDialog() {
|
||||
val layout = LinearLayout(requireContext())
|
||||
layout.orientation = LinearLayout.VERTICAL
|
||||
layout.addView(TextView(requireContext()).apply { setText(R.string.load_will_overwrite) })
|
||||
val et = EditText(requireContext())
|
||||
layout.addView(et)
|
||||
val padding = ResourceUtils.toPx(8, resources)
|
||||
layout.setPadding(3 * padding, padding, padding, padding)
|
||||
val d = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.load)
|
||||
.setView(layout)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
loadColorString(et.text.toString())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.button_load_custom) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/*", "application/octet-stream", "application/json"))
|
||||
.setType("*/*")
|
||||
loadFilePicker.launch(intent)
|
||||
}
|
||||
.create()
|
||||
et.doAfterTextChanged { d.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = et.text.toString().isNotBlank() }
|
||||
d.show()
|
||||
d.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = false
|
||||
}
|
||||
|
||||
private fun loadColorString(colorString: String) {
|
||||
// show dialog
|
||||
// load from file or from text field
|
||||
// do some sanity check (only write correct settings, consider current night mode)
|
||||
try {
|
||||
val that = Json.decodeFromString<SaveThoseColors>(colorString)
|
||||
// save mode to moreColors and PREF_SHOW_MORE_COLORS (with night dependence!)
|
||||
that.colors.forEach {
|
||||
val pref = Settings.getColorPref(it.key, isNight)
|
||||
if (it.value.first == null)
|
||||
prefs.edit { remove(pref) }
|
||||
else prefs.edit { putInt(pref, it.value.first!!) }
|
||||
prefs.edit { putBoolean(pref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, it.value.second) }
|
||||
}
|
||||
moreColors = that.moreColors
|
||||
} catch (e: SerializationException) {
|
||||
try {
|
||||
val allColorsStringMap = Json.decodeFromString<Map<String, Int>>(colorString)
|
||||
val allColors = EnumMap<ColorType, Int>(ColorType::class.java)
|
||||
allColorsStringMap.forEach {
|
||||
try {
|
||||
allColors[ColorType.valueOf(it.key)] = it.value
|
||||
} catch (_: IllegalArgumentException) {}
|
||||
}
|
||||
writeAllColorsMap(allColors, prefs, isNight)
|
||||
moreColors = 2
|
||||
} catch (e: SerializationException) {
|
||||
infoDialog(requireContext(), "error")
|
||||
}
|
||||
}
|
||||
updateColorPrefs()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
}
|
||||
|
||||
private fun getColorString(): String {
|
||||
if (moreColors == 2)
|
||||
return Json.encodeToString(readAllColorsMap(prefs, isNight).map { it.key.name to it.value }.toMap())
|
||||
// read the actual prefs!
|
||||
val colors = colorPrefsAndResIds.associate {
|
||||
val pref = Settings.getColorPref(it.first, isNight)
|
||||
val color = if (prefs.contains(pref)) prefs.getInt(pref, 0) else null
|
||||
it.first to (color to prefs.getBoolean(pref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, true))
|
||||
}
|
||||
return Json.encodeToString(SaveThoseColors(moreColors, colors))
|
||||
}
|
||||
|
||||
private val saveFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
activity?.contentResolver?.openOutputStream(uri)?.writer()?.use { it.write(getColorString()) }
|
||||
}
|
||||
|
||||
private val loadFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
activity?.contentResolver?.openInputStream(uri)?.use {
|
||||
loadColorString(it.reader().readText())
|
||||
} ?: infoDialog(requireContext(), R.string.file_read_error)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
class ColorsNightSettingsFragment : ColorsSettingsFragment() {
|
||||
// override val isNight = true
|
||||
// override val titleResId = R.string.select_user_colors_night
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import static helium314.keyboard.latin.permissions.PermissionsManager.get;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.permissions.PermissionsManager;
|
||||
import helium314.keyboard.latin.permissions.PermissionsUtil;
|
||||
|
||||
public final class CorrectionSettingsFragment extends SubScreenFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
PermissionsManager.PermissionsResultCallback {
|
||||
|
||||
private SwitchPreference mLookupContactsPreference;
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_correction);
|
||||
|
||||
mLookupContactsPreference = findPreference(Settings.PREF_USE_CONTACTS);
|
||||
|
||||
refreshEnabledSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
if (Settings.PREF_USE_CONTACTS.equals(key)
|
||||
&& prefs.getBoolean(key, false)
|
||||
&& !PermissionsUtil.checkAllPermissionsGranted(getActivity(), Manifest.permission.READ_CONTACTS)
|
||||
) {
|
||||
get(requireContext()).requestPermissions(this, getActivity(), Manifest.permission.READ_CONTACTS);
|
||||
} else if (Settings.PREF_KEY_USE_PERSONALIZED_DICTS.equals(key) && !prefs.getBoolean(key, true)) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.disable_personalized_dicts_message)
|
||||
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> ((TwoStatePreference) findPreference(key)).setChecked(true))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setOnCancelListener(dialogInterface -> ((TwoStatePreference) findPreference(key)).setChecked(true))
|
||||
.show();
|
||||
} else if (Settings.PREF_SHOW_SUGGESTIONS.equals(key) && !prefs.getBoolean(key, true)) {
|
||||
((TwoStatePreference)findPreference(Settings.PREF_ALWAYS_SHOW_SUGGESTIONS)).setChecked(false);
|
||||
} else if (Settings.PREF_BIGRAM_PREDICTIONS.equals(key)) {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext());
|
||||
}
|
||||
refreshEnabledSettings();
|
||||
}
|
||||
|
||||
// contacts and permission stuff from SpellCheckerSettingsFragment
|
||||
@Override
|
||||
public void onRequestPermissionsResult(boolean allGranted) {
|
||||
turnOffLookupContactsIfNoPermission();
|
||||
if (allGranted)
|
||||
mLookupContactsPreference.setChecked(true);
|
||||
}
|
||||
|
||||
private void turnOffLookupContactsIfNoPermission() {
|
||||
if (!PermissionsUtil.checkAllPermissionsGranted(
|
||||
getActivity(), Manifest.permission.READ_CONTACTS)) {
|
||||
mLookupContactsPreference.setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshEnabledSettings() {
|
||||
setPreferenceVisible(Settings.PREF_AUTO_CORRECTION_CONFIDENCE, getSharedPreferences().getBoolean(Settings.PREF_AUTO_CORRECTION, Defaults.PREF_AUTO_CORRECTION));
|
||||
setPreferenceVisible(Settings.PREF_MORE_AUTO_CORRECTION, getSharedPreferences().getBoolean(Settings.PREF_AUTO_CORRECTION, Defaults.PREF_AUTO_CORRECTION));
|
||||
setPreferenceVisible(Settings.PREF_ADD_TO_PERSONAL_DICTIONARY, getSharedPreferences().getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true));
|
||||
setPreferenceVisible(Settings.PREF_ALWAYS_SHOW_SUGGESTIONS, getSharedPreferences().getBoolean(Settings.PREF_SHOW_SUGGESTIONS, true));
|
||||
setPreferenceVisible(Settings.PREF_CENTER_SUGGESTION_TEXT_TO_ENTER, getSharedPreferences().getBoolean(Settings.PREF_SHOW_SUGGESTIONS, true));
|
||||
turnOffLookupContactsIfNoPermission();
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ public final class DebugSettings {
|
|||
public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
|
||||
public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "sliding_key_input_preview";
|
||||
public static final String PREF_SHOW_DEBUG_SETTINGS = "show_debug_settings";
|
||||
public static final String PREF_KEY_DUMP_DICT_PREFIX = "dump_dictionaries";
|
||||
|
||||
public static final String PREF_SHOW_SUGGESTION_INFOS = "show_suggestion_infos";
|
||||
private DebugSettings() {
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher;
|
||||
import helium314.keyboard.latin.BuildConfig;
|
||||
import helium314.keyboard.latin.DictionaryDumpBroadcastReceiver;
|
||||
import helium314.keyboard.latin.DictionaryFacilitator;
|
||||
import helium314.keyboard.latin.R;
|
||||
|
||||
/**
|
||||
* "Debug mode" settings sub screen.
|
||||
* <p>
|
||||
* This settings sub screen handles a several preference options for debugging.
|
||||
*/
|
||||
public final class DebugSettingsFragment extends SubScreenFragment
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
private static final String PREF_KEY_DUMP_DICTS = "dump_dictionaries";
|
||||
public static final String PREF_KEY_DUMP_DICT_PREFIX = "dump_dictionaries";
|
||||
|
||||
private boolean mServiceNeedsRestart = false;
|
||||
private TwoStatePreference mDebugMode;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_debug);
|
||||
|
||||
final PreferenceGroup dictDumpPreferenceGroup = findPreference(PREF_KEY_DUMP_DICTS);
|
||||
for (final String dictName : DictionaryFacilitator.DYNAMIC_DICTIONARY_TYPES) {
|
||||
final Preference pref = new DictDumpPreference(getActivity(), dictName);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
dictDumpPreferenceGroup.addPreference(pref);
|
||||
}
|
||||
if (BuildConfig.DEBUG)
|
||||
removePreference(DebugSettings.PREF_SHOW_DEBUG_SETTINGS);
|
||||
|
||||
mServiceNeedsRestart = false;
|
||||
mDebugMode = findPreference(DebugSettings.PREF_DEBUG_MODE);
|
||||
findPreference(DebugSettings.PREF_SHOW_SUGGESTION_INFOS).setVisible(mDebugMode.isChecked());
|
||||
updateDebugMode();
|
||||
}
|
||||
|
||||
private static class DictDumpPreference extends Preference {
|
||||
public final String mDictName;
|
||||
|
||||
public DictDumpPreference(final Context context, final String dictName) {
|
||||
super(context);
|
||||
setKey(PREF_KEY_DUMP_DICT_PREFIX + dictName);
|
||||
setTitle("Dump " + dictName + " dictionary");
|
||||
mDictName = dictName;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull final Preference pref) {
|
||||
if (pref instanceof final DictDumpPreference dictDumpPref) {
|
||||
final String dictName = dictDumpPref.mDictName;
|
||||
final Intent intent = new Intent(
|
||||
DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
|
||||
intent.putExtra(DictionaryDumpBroadcastReceiver.DICTIONARY_NAME_KEY, dictName);
|
||||
pref.getContext().sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (mServiceNeedsRestart) {
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
if (DebugSettings.PREF_DEBUG_MODE.equals(key) && mDebugMode != null) {
|
||||
final boolean enabled = prefs.getBoolean(DebugSettings.PREF_DEBUG_MODE, false);
|
||||
mDebugMode.setChecked(enabled);
|
||||
findPreference(DebugSettings.PREF_SHOW_SUGGESTION_INFOS).setVisible(enabled);
|
||||
mServiceNeedsRestart = true;
|
||||
} else if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
|
||||
mServiceNeedsRestart = true;
|
||||
} else if (key.equals(DebugSettings.PREF_SHOW_SUGGESTION_INFOS)) {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext());
|
||||
} else if (key.equals(DebugSettings.PREF_SHOW_DEBUG_SETTINGS) && mDebugMode.isChecked()) {
|
||||
mDebugMode.setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDebugMode() {
|
||||
final String version = getString(R.string.version_text, BuildConfig.VERSION_NAME);
|
||||
mDebugMode.setSummary(version);
|
||||
}
|
||||
|
||||
}
|
|
@ -62,7 +62,7 @@ object Defaults {
|
|||
var PREF_POPUP_ON = true
|
||||
const val PREF_AUTO_CORRECTION = true
|
||||
const val PREF_MORE_AUTO_CORRECTION = false
|
||||
const val PREF_AUTO_CORRECTION_CONFIDENCE = "0"
|
||||
const val PREF_AUTO_CORRECT_THRESHOLD = 0.185f
|
||||
const val PREF_AUTOCORRECT_SHORTCUTS = true
|
||||
const val PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = false
|
||||
const val PREF_SHOW_SUGGESTIONS = true
|
||||
|
@ -128,7 +128,6 @@ object Defaults {
|
|||
const val PREF_LANGUAGE_SWIPE_DISTANCE = 5
|
||||
const val PREF_ENABLE_CLIPBOARD_HISTORY = true
|
||||
const val PREF_CLIPBOARD_HISTORY_RETENTION_TIME = 10 // minutes
|
||||
const val PREF_SECONDARY_LOCALES = ""
|
||||
const val PREF_ADD_TO_PERSONAL_DICTIONARY = false
|
||||
@JvmField
|
||||
val PREF_NAVBAR_COLOR = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
// taken from StreetComplete, ViewBinder.kt
|
||||
inline fun <reified T : ViewBinding> Fragment.viewBinding(
|
||||
noinline viewBinder: (View) -> T,
|
||||
rootViewId: Int? = null
|
||||
) = FragmentViewBindingPropertyDelegate(this, viewBinder, rootViewId)
|
||||
|
||||
class FragmentViewBindingPropertyDelegate<T : ViewBinding>(
|
||||
private val fragment: Fragment,
|
||||
private val viewBinder: (View) -> T,
|
||||
private val rootViewId: Int? = null
|
||||
) : ReadOnlyProperty<Fragment, T>, LifecycleEventObserver {
|
||||
|
||||
private var binding: T? = null
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
if (event == Lifecycle.Event.ON_DESTROY) {
|
||||
binding = null
|
||||
source.lifecycle.removeObserver(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||
if (binding == null) {
|
||||
val rootView = if (rootViewId != null) {
|
||||
thisRef.requireView().findViewById<ViewGroup>(rootViewId)!!.getChildAt(0)
|
||||
} else {
|
||||
thisRef.requireView()
|
||||
}
|
||||
binding = viewBinder(rootView)
|
||||
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||
}
|
||||
return binding!!
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher;
|
||||
import helium314.keyboard.latin.R;
|
||||
|
||||
/**
|
||||
* "Gesture typing preferences" settings sub screen.
|
||||
* <p>
|
||||
* This settings sub screen handles the following gesture typing preferences.
|
||||
* - Enable gesture typing
|
||||
* - Dynamic floating preview
|
||||
* - Show gesture trail
|
||||
* - Phrase gesture
|
||||
*/
|
||||
public final class GestureSettingsFragment extends SubScreenFragment {
|
||||
private boolean needsReload = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_gesture);
|
||||
setupGestureDynamicPreviewPref();
|
||||
setupGestureFastTypingCooldownPref();
|
||||
setupGestureTrailFadeoutPref();
|
||||
refreshSettingsEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (needsReload) {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext());
|
||||
needsReload = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
refreshSettingsEnablement();
|
||||
}
|
||||
|
||||
private void refreshSettingsEnablement() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final boolean gestureInputEnabled = prefs.getBoolean(Settings.PREF_GESTURE_INPUT, Defaults.PREF_GESTURE_INPUT);
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled);
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled);
|
||||
final boolean gesturePreviewEnabled = gestureInputEnabled
|
||||
&& prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_FLOATING_PREVIEW_DYNAMIC, gesturePreviewEnabled);
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_SPACE_AWARE, gestureInputEnabled);
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_FAST_TYPING_COOLDOWN, gestureInputEnabled);
|
||||
final boolean gestureTrailEnabled = gestureInputEnabled
|
||||
&& prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
|
||||
// This setting also affects the preview linger duration, so it's visible if either setting is enabled.
|
||||
setPreferenceVisible(Settings.PREF_GESTURE_TRAIL_FADEOUT_DURATION, gestureTrailEnabled || gesturePreviewEnabled);
|
||||
}
|
||||
|
||||
private void setupGestureDynamicPreviewPref() {
|
||||
final SwitchPreference pref = findPreference(Settings.PREF_GESTURE_FLOATING_PREVIEW_DYNAMIC);
|
||||
if (pref == null) return;
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
pref.setChecked(Settings.readGestureDynamicPreviewEnabled(prefs));
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// default value is based on system reduced motion
|
||||
final boolean defValue = Settings.readGestureDynamicPreviewDefault(requireContext());
|
||||
final boolean followingSystem = newValue.equals(defValue);
|
||||
// allow the default to be overridden
|
||||
prefs.edit().putBoolean(Settings.PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM, followingSystem).apply();
|
||||
needsReload = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setupGestureFastTypingCooldownPref() {
|
||||
final SeekBarDialogPreference pref = findPreference(
|
||||
Settings.PREF_GESTURE_FAST_TYPING_COOLDOWN);
|
||||
if (pref == null) return;
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return prefs.getInt(Settings.PREF_GESTURE_FAST_TYPING_COOLDOWN, Defaults.PREF_GESTURE_FAST_TYPING_COOLDOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return Settings.readDefaultGestureFastTypingCooldown(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
if (value == 0) {
|
||||
return res.getString(R.string.gesture_fast_typing_cooldown_instant);
|
||||
}
|
||||
return res.getString(R.string.abbreviation_unit_milliseconds, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupGestureTrailFadeoutPref() {
|
||||
final SeekBarDialogPreference pref = findPreference(Settings.PREF_GESTURE_TRAIL_FADEOUT_DURATION);
|
||||
if (pref == null) return;
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putInt(key, value).apply();
|
||||
needsReload = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
needsReload = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return prefs.getInt(Settings.PREF_GESTURE_TRAIL_FADEOUT_DURATION, Defaults.PREF_GESTURE_TRAIL_FADEOUT_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return 800;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
// fade-out has a constant start delay, value text is adjusted accordingly.
|
||||
final int adjustedValue = res.getInteger(R.integer.config_gesture_trail_fadeout_start_delay) + value;
|
||||
return res.getString(R.string.abbreviation_unit_milliseconds, String.valueOf(adjustedValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.LocaleUtils
|
||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||
import helium314.keyboard.latin.utils.displayName
|
||||
import helium314.keyboard.latin.utils.locale
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
import helium314.keyboard.latin.utils.showMissingDictionaryDialog
|
||||
import java.util.Locale
|
||||
|
||||
class LanguageFilterList(searchField: EditText, recyclerView: RecyclerView) {
|
||||
|
||||
private val adapter = LanguageAdapter(emptyList(), recyclerView.context)
|
||||
private val sortedSubtypes = mutableListOf<MutableList<SubtypeInfo>>()
|
||||
|
||||
fun setSettingsFragment(newFragment: LanguageSettingsFragment?) {
|
||||
adapter.fragment = newFragment
|
||||
}
|
||||
|
||||
init {
|
||||
recyclerView.adapter = adapter
|
||||
searchField.doAfterTextChanged { text ->
|
||||
adapter.list = sortedSubtypes.filter { it.first().displayName.startsWith(text.toString(), ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setLanguages(list: Collection<MutableList<SubtypeInfo>>, onlySystemLocales: Boolean) {
|
||||
sortedSubtypes.clear()
|
||||
sortedSubtypes.addAll(list)
|
||||
adapter.onlySystemLocales = onlySystemLocales
|
||||
adapter.list = sortedSubtypes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class LanguageAdapter(list: List<MutableList<SubtypeInfo>> = listOf(), context: Context) :
|
||||
RecyclerView.Adapter<LanguageAdapter.ViewHolder>() {
|
||||
var onlySystemLocales = false
|
||||
private val prefs = context.prefs()
|
||||
var fragment: LanguageSettingsFragment? = null
|
||||
|
||||
var list: List<MutableList<SubtypeInfo>> = list
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.onBind(list[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = list.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LanguageAdapter.ViewHolder {
|
||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.language_list_item, parent, false)
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun onBind(infos: MutableList<SubtypeInfo>) {
|
||||
sort(infos)
|
||||
fun setupDetailsTextAndSwitch() {
|
||||
view.findViewById<TextView>(R.id.language_details).apply {
|
||||
// input styles if more than one in infos
|
||||
val sb = SpannableStringBuilder()
|
||||
if (infos.size > 1 && !onlySystemLocales) {
|
||||
var start = true
|
||||
infos.forEach {
|
||||
val string = SpannableString(SubtypeLocaleUtils.getMainLayoutDisplayName(it.subtype)
|
||||
?: it.subtype.displayName(context))
|
||||
if (it.isEnabled)
|
||||
string.setSpan(StyleSpan(Typeface.BOLD), 0, string.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
if (!start) {
|
||||
sb.append(", ")
|
||||
}
|
||||
start = false
|
||||
sb.append(string)
|
||||
}
|
||||
}
|
||||
val secondaryLocales = emptyList<Locale>()
|
||||
if (secondaryLocales.isNotEmpty()) {
|
||||
if (sb.isNotEmpty())
|
||||
sb.append("\n")
|
||||
//sb.append(Settings.getSecondaryLocales(prefs, infos.first().subtype.locale())
|
||||
// .joinToString(", ") {
|
||||
// LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context)
|
||||
// })
|
||||
}
|
||||
text = sb
|
||||
if (text.isBlank()) isGone = true
|
||||
else isVisible = true
|
||||
}
|
||||
|
||||
view.findViewById<Switch>(R.id.language_switch).apply {
|
||||
isEnabled = !onlySystemLocales
|
||||
// take care: isChecked changes if the language is scrolled out of view and comes back!
|
||||
// disable the change listener when setting the checked status on scroll
|
||||
// so it's only triggered on user interactions
|
||||
setOnCheckedChangeListener(null)
|
||||
isChecked = onlySystemLocales || infos.any { it.isEnabled }
|
||||
setOnCheckedChangeListener { _, b ->
|
||||
if (b) {
|
||||
if (!infos.first().hasDictionary)
|
||||
showMissingDictionaryDialog(context, infos.first().subtype.locale())
|
||||
SubtypeSettings.addEnabledSubtype(prefs, infos.first().subtype)
|
||||
infos.first().isEnabled = true
|
||||
} else {
|
||||
SubtypeSettings.removeEnabledSubtype(context, infos.first().subtype)
|
||||
infos.first().isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
view.findViewById<TextView>(R.id.blocker).apply {
|
||||
if (infos.size < 2) {
|
||||
isGone = true
|
||||
return@apply
|
||||
}
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
LanguageSettingsDialog(view.context, infos, fragment, onlySystemLocales, { setupDetailsTextAndSwitch() }).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.findViewById<TextView>(R.id.language_name).text = infos.first().displayName
|
||||
view.findViewById<LinearLayout>(R.id.language_text).setOnClickListener {
|
||||
LanguageSettingsDialog(view.context, infos, fragment, onlySystemLocales, { setupDetailsTextAndSwitch() }).show()
|
||||
}
|
||||
setupDetailsTextAndSwitch()
|
||||
}
|
||||
|
||||
private fun sort(infos: MutableList<SubtypeInfo>) {
|
||||
if (infos.size <= 1) return
|
||||
infos.sortWith(compareBy({ SubtypeSettings.isAdditionalSubtype(it.subtype) }, { it.displayName }))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,430 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.ScrollView
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.size
|
||||
import helium314.keyboard.compat.locale
|
||||
import helium314.keyboard.dictionarypack.DictionaryPackConstants
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.Links.DICTIONARY_URL
|
||||
import helium314.keyboard.latin.common.Links.LAYOUT_FORMAT_URL
|
||||
import helium314.keyboard.latin.common.LocaleUtils
|
||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||
import helium314.keyboard.latin.databinding.LanguageListItemBinding
|
||||
import helium314.keyboard.latin.databinding.LocaleSettingsDialogBinding
|
||||
import helium314.keyboard.latin.utils.*
|
||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils.USER_DICTIONARY_SUFFIX
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class LanguageSettingsDialog(
|
||||
context: Context,
|
||||
private val infos: MutableList<SubtypeInfo>,
|
||||
private val fragment: LanguageSettingsFragment?,
|
||||
private val onlySystemLocales: Boolean,
|
||||
private val reloadSetting: () -> Unit
|
||||
) : AlertDialog(context), LanguageSettingsFragment.Listener {
|
||||
private val prefs = context.prefs()
|
||||
private val binding = LocaleSettingsDialogBinding.inflate(LayoutInflater.from(context))
|
||||
private val mainLocale = infos.first().subtype.locale()
|
||||
private var hasInternalDictForLanguage = false
|
||||
private val userDicts = mutableSetOf<File>()
|
||||
|
||||
init {
|
||||
setTitle(infos.first().displayName)
|
||||
setView(ScrollView(context).apply { addView(binding.root) })
|
||||
setButton(BUTTON_NEGATIVE, context.getString(R.string.dialog_close)) { _, _ ->
|
||||
dismiss()
|
||||
}
|
||||
|
||||
if (onlySystemLocales)
|
||||
// don't allow setting subtypes, because
|
||||
// a. subtypes are set purely based on system locales (in SubtypeSettings)
|
||||
// b. extra handling needed if user disables all subtypes for a locale
|
||||
// todo (later): fix above and allow it
|
||||
binding.subtypes.isGone = true
|
||||
else
|
||||
fillSubtypesView()
|
||||
fillSecondaryLocaleView()
|
||||
fillDictionariesView()
|
||||
setupPopupSettings()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
fragment?.setListener(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
fragment?.setListener(null)
|
||||
}
|
||||
|
||||
private fun fillSubtypesView() {
|
||||
if (infos.first().subtype.isAsciiCapable) {
|
||||
binding.addSubtype.setOnClickListener {
|
||||
val layouts = context.resources.getStringArray(R.array.predefined_layouts)
|
||||
.filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getMainLayoutName(it.subtype) == layoutName } }
|
||||
val displayNames = layouts.map { SubtypeLocaleUtils.getMainLayoutDisplayName(it) }
|
||||
Builder(context)
|
||||
.setTitle(R.string.keyboard_layout_set)
|
||||
.setItems(displayNames.toTypedArray()) { di, i ->
|
||||
di.dismiss()
|
||||
addSubtype(layouts[i])
|
||||
}
|
||||
.setNeutralButton(R.string.button_title_add_custom_layout) { _, _ -> onClickAddCustomSubtype() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
} else
|
||||
binding.addSubtype.setOnClickListener { onClickAddCustomSubtype() }
|
||||
|
||||
// add subtypes
|
||||
infos.sortedBy { it.displayName }.forEach {
|
||||
addSubtypeToView(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSubtype(name: String) {
|
||||
LayoutUtilsCustom.onLayoutFileChanged()
|
||||
val newSubtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable)
|
||||
val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default
|
||||
val displayName = SubtypeLocaleUtils.getMainLayoutDisplayName(newSubtype)
|
||||
val old = infos.firstOrNull { SubtypeSettings.isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getMainLayoutDisplayName(it.subtype) }
|
||||
if (old != null) {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
|
||||
reloadSetting()
|
||||
return
|
||||
}
|
||||
|
||||
SubtypeUtilsAdditional.addAdditionalSubtype(prefs, newSubtype)
|
||||
SubtypeSettings.addEnabledSubtype(prefs, newSubtype)
|
||||
addSubtypeToView(newSubtypeInfo)
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged()
|
||||
infos.add(newSubtypeInfo)
|
||||
reloadSetting()
|
||||
}
|
||||
|
||||
private fun onClickAddCustomSubtype() {
|
||||
val link = "<a href='$LAYOUT_FORMAT_URL'>" + context.getString(R.string.dictionary_link_text) + "</a>"
|
||||
val message = SpannableStringUtils.fromHtml(context.getString(R.string.message_add_custom_layout, link))
|
||||
val dialog = Builder(context)
|
||||
.setTitle(R.string.button_title_add_custom_layout)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.button_copy_existing_layout) { _, _ -> copyLayout() }
|
||||
.setPositiveButton(R.string.button_load_custom) { _, _ -> fragment?.requestLayoutFile() }
|
||||
.create()
|
||||
dialog.show()
|
||||
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun copyLayout() {
|
||||
val layouts = mutableListOf<String>()
|
||||
val displayNames = mutableListOf<String>()
|
||||
infos.forEach {
|
||||
val mainLayoutName = it.subtype.mainLayoutName() ?: "qwerty"
|
||||
if (!LayoutUtilsCustom.isCustomLayout(mainLayoutName) // don't allow copying custom layout (at least for now)
|
||||
&& !mainLayoutName.endsWith("+")) { // don't allow copying layouts only defined via extra keys
|
||||
layouts.add(mainLayoutName)
|
||||
displayNames.add(it.subtype.displayName(context).toString())
|
||||
}
|
||||
}
|
||||
if (infos.first().subtype.isAsciiCapable) {
|
||||
context.resources.getStringArray(R.array.predefined_layouts).forEach {
|
||||
layouts.add(it)
|
||||
displayNames.add(SubtypeLocaleUtils.getMainLayoutDisplayName(it) ?: it)
|
||||
}
|
||||
}
|
||||
Builder(context)
|
||||
.setTitle(R.string.keyboard_layout_set)
|
||||
.setItems(displayNames.toTypedArray()) { di, i ->
|
||||
di.dismiss()
|
||||
val fileName = context.assets.list("layouts")?.firstOrNull { it.startsWith(layouts[i]) } ?: return@setItems
|
||||
LayoutUtilsCustom.loadLayout(context.assets.open("layouts${File.separator}$fileName").reader().readText(),
|
||||
displayNames[i], mainLocale.toLanguageTag(), context) { addSubtype(it) }
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onNewLayoutFile(uri: Uri?) {
|
||||
LayoutUtilsCustom.loadLayout(uri, mainLocale.toLanguageTag(), context) { addSubtype(it) }
|
||||
}
|
||||
|
||||
private fun addSubtypeToView(subtype: SubtypeInfo) {
|
||||
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
|
||||
val layoutSetName = subtype.subtype.mainLayoutName() ?: "qwerty"
|
||||
row.findViewById<TextView>(R.id.language_name).text =
|
||||
SubtypeLocaleUtils.getMainLayoutDisplayName(subtype.subtype)
|
||||
?: subtype.subtype.displayName(context)
|
||||
if (LayoutUtilsCustom.isCustomLayout(layoutSetName)) {
|
||||
row.findViewById<TextView>(R.id.language_details).setText(R.string.edit_layout)
|
||||
row.findViewById<View>(R.id.language_text).setOnClickListener { LayoutUtilsCustom.editLayout(layoutSetName, context) }
|
||||
} else {
|
||||
row.findViewById<View>(R.id.language_details).isGone = true
|
||||
}
|
||||
row.findViewById<Switch>(R.id.language_switch).apply {
|
||||
isChecked = subtype.isEnabled
|
||||
isEnabled = !onlySystemLocales
|
||||
setOnCheckedChangeListener { _, b ->
|
||||
if (b) {
|
||||
if (!infos.first().hasDictionary)
|
||||
showMissingDictionaryDialog(context, mainLocale)
|
||||
SubtypeSettings.addEnabledSubtype(prefs, subtype.subtype)
|
||||
}
|
||||
else
|
||||
SubtypeSettings.removeEnabledSubtype(context, subtype.subtype)
|
||||
subtype.isEnabled = b
|
||||
reloadSetting()
|
||||
}
|
||||
}
|
||||
if (SubtypeSettings.isAdditionalSubtype(subtype.subtype)) {
|
||||
row.findViewById<Switch>(R.id.language_switch).isEnabled = true
|
||||
row.findViewById<ImageView>(R.id.delete_button).apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
val isCustom = LayoutUtilsCustom.isCustomLayout(layoutSetName)
|
||||
fun delete() {
|
||||
binding.subtypes.removeView(row)
|
||||
infos.remove(subtype)
|
||||
//if (isCustom)
|
||||
// LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context)
|
||||
SubtypeUtilsAdditional.removeAdditionalSubtype(context, subtype.subtype)
|
||||
SubtypeSettings.removeEnabledSubtype(context, subtype.subtype)
|
||||
reloadSetting()
|
||||
}
|
||||
if (isCustom) {
|
||||
confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
||||
} else {
|
||||
delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.subtypes.addView(row)
|
||||
}
|
||||
|
||||
private fun fillSecondaryLocaleView() {
|
||||
// can only use multilingual typing if there is more than one dictionary available
|
||||
val availableSecondaryLocales = getAvailableSecondaryLocales(
|
||||
context,
|
||||
mainLocale,
|
||||
infos.first().subtype.isAsciiCapable
|
||||
)
|
||||
val selectedSecondaryLocales = emptyList<Locale>()// Settings.getSecondaryLocales(prefs, mainLocale)
|
||||
selectedSecondaryLocales.forEach {
|
||||
addSecondaryLocaleView(it)
|
||||
}
|
||||
if (availableSecondaryLocales.isNotEmpty()) {
|
||||
binding.addSecondaryLanguage.apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
val locales = (availableSecondaryLocales).sortedBy { it.displayName }
|
||||
val localeNames = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray()
|
||||
Builder(context)
|
||||
.setTitle(R.string.button_select_language)
|
||||
.setItems(localeNames) { di, i ->
|
||||
val locale = locales[i]
|
||||
//val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale)
|
||||
//Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales + locale)
|
||||
addSecondaryLocaleView(locale)
|
||||
di.dismiss()
|
||||
reloadSetting()
|
||||
reloadDictionaries()
|
||||
KeyboardLayoutSet.onSystemLocaleChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else if (selectedSecondaryLocales.isEmpty())
|
||||
binding.secondaryLocales.isGone = true
|
||||
}
|
||||
|
||||
private fun addSecondaryLocaleView(locale: Locale) {
|
||||
val rowBinding = LanguageListItemBinding.inflate(LayoutInflater.from(context), listView, false)
|
||||
rowBinding.languageSwitch.isGone = true
|
||||
rowBinding.languageDetails.isGone = true
|
||||
rowBinding.languageName.text = locale.displayName
|
||||
rowBinding.deleteButton.apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
//val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale)
|
||||
//Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales - locale)
|
||||
binding.secondaryLocales.removeView(rowBinding.root)
|
||||
reloadSetting()
|
||||
reloadDictionaries()
|
||||
KeyboardLayoutSet.onSystemLocaleChanged()
|
||||
}
|
||||
}
|
||||
binding.secondaryLocales.addView(rowBinding.root)
|
||||
}
|
||||
|
||||
private fun fillDictionariesView() {
|
||||
binding.addDictionary.setOnClickListener {
|
||||
val dictLink = "<a href='$DICTIONARY_URL'>" + context.getString(R.string.dictionary_link_text) + "</a>"
|
||||
val startMessage = context.getString(R.string.add_dictionary, dictLink)
|
||||
val messageRawText = createDictionaryTextHtml(startMessage, mainLocale, context)
|
||||
val message = SpannableStringUtils.fromHtml(messageRawText)
|
||||
val dialog = Builder(context)
|
||||
.setTitle(R.string.add_new_dictionary_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.user_dict_settings_add_menu_title) { _, _ -> fragment?.requestDictionary() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
dialog.show()
|
||||
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
val userDictsAndHasInternal = getUserAndInternalDictionaries(context, mainLocale)
|
||||
hasInternalDictForLanguage = userDictsAndHasInternal.second
|
||||
userDicts.addAll(userDictsAndHasInternal.first)
|
||||
if (hasInternalDictForLanguage) {
|
||||
binding.dictionaries.addView(TextView(context, null, R.style.PreferenceCategoryTitleText).apply {
|
||||
setText(R.string.internal_dictionary_summary)
|
||||
// just setting a text size can be complicated...
|
||||
val attrs = context.obtainStyledAttributes(R.style.PreferenceSubtitleText, intArrayOf(android.R.attr.textSize))
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, attrs.getDimension(0, 20f))
|
||||
attrs.recycle()
|
||||
setPadding(ResourceUtils.toPx(16, context.resources), 0, 0, 0)
|
||||
isEnabled = userDicts.none { it.name == "${DictionaryInfoUtils.MAIN_DICT_PREFIX}${USER_DICTIONARY_SUFFIX}" }
|
||||
})
|
||||
}
|
||||
userDicts.sorted().forEach {
|
||||
addDictionaryToView(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewDictionary(uri: Uri?) {
|
||||
NewDictionaryAdder(context) { replaced, dictFile ->
|
||||
if (!replaced) {
|
||||
addDictionaryToView(dictFile)
|
||||
userDicts.add(dictFile)
|
||||
if (hasInternalDictForLanguage) {
|
||||
binding.dictionaries[1].isEnabled =
|
||||
userDicts.none { it.name == "${DictionaryInfoUtils.MAIN_DICT_PREFIX}${USER_DICTIONARY_SUFFIX}" }
|
||||
}
|
||||
}
|
||||
}.addDictionary(uri, mainLocale)
|
||||
}
|
||||
|
||||
private fun addDictionaryToView(dictFile: File) {
|
||||
if (!infos.first().hasDictionary) {
|
||||
infos.forEach { it.hasDictionary = true }
|
||||
}
|
||||
val dictType = dictFile.name.substringBefore("_${USER_DICTIONARY_SUFFIX}")
|
||||
val rowBinding = LanguageListItemBinding.inflate(LayoutInflater.from(context), listView, false)
|
||||
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())
|
||||
rowBinding.languageName.text = dictType
|
||||
rowBinding.languageDetails.apply {
|
||||
if (header?.description == null) {
|
||||
isGone = true
|
||||
} else {
|
||||
// what would potentially be interesting? locale? description? version? timestamp?
|
||||
text = header.description
|
||||
}
|
||||
}
|
||||
rowBinding.languageText.setOnClickListener {
|
||||
if (header == null) return@setOnClickListener
|
||||
val locale = context.resources.configuration.locale()
|
||||
Builder(context)
|
||||
.setMessage(header.info(locale))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
rowBinding.languageSwitch.isGone = true
|
||||
rowBinding.deleteButton.apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
confirmDialog(context, context.getString(R.string.remove_dictionary_message, dictType), context.getString(
|
||||
R.string.delete)) {
|
||||
val parent = dictFile.parentFile
|
||||
dictFile.delete()
|
||||
if (parent?.list()?.isEmpty() == true)
|
||||
parent.delete()
|
||||
reloadDictionaries()
|
||||
binding.dictionaries.removeView(rowBinding.root)
|
||||
if (binding.dictionaries.size < 2) { // first view is "Dictionaries"
|
||||
infos.forEach { it.hasDictionary = false }
|
||||
}
|
||||
userDicts.remove(dictFile)
|
||||
if (hasInternalDictForLanguage) {
|
||||
binding.dictionaries[1].isEnabled =
|
||||
userDicts.none { it.name == "${DictionaryInfoUtils.MAIN_DICT_PREFIX}${USER_DICTIONARY_SUFFIX}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.dictionaries.addView(rowBinding.root)
|
||||
}
|
||||
|
||||
private fun setupPopupSettings() {
|
||||
binding.popupOrder.setOnClickListener {
|
||||
val popupKeyTypesDefault = prefs.getString(Settings.PREF_POPUP_KEYS_ORDER, POPUP_KEYS_ORDER_DEFAULT)!!
|
||||
reorderDialog(context, Settings.PREF_POPUP_KEYS_ORDER + "_" + mainLocale.toLanguageTag(), popupKeyTypesDefault, R.string.popup_order)
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged()
|
||||
}
|
||||
binding.popupLabelPriority.setOnClickListener {
|
||||
val popupKeyTypesDefault = prefs.getString(Settings.PREF_POPUP_KEYS_LABELS_ORDER, POPUP_KEYS_LABEL_DEFAULT)!!
|
||||
reorderDialog(context, Settings.PREF_POPUP_KEYS_LABELS_ORDER + "_" + mainLocale.toLanguageTag(), popupKeyTypesDefault, R.string.hint_source)
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadDictionaries() = fragment?.activity?.sendBroadcast(Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION))
|
||||
}
|
||||
|
||||
/** @return list of user dictionary files and whether an internal dictionary exists */
|
||||
private fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
|
||||
val userDicts = mutableListOf<File>()
|
||||
var hasInternalDict = false
|
||||
val userLocaleDir = File(DictionaryInfoUtils.getAndCreateCacheDirectoryForLocale(locale, context))
|
||||
if (userLocaleDir.exists() && userLocaleDir.isDirectory) {
|
||||
userLocaleDir.listFiles()?.forEach {
|
||||
if (it.name.endsWith(USER_DICTIONARY_SUFFIX))
|
||||
userDicts.add(it)
|
||||
else if (it.name.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX))
|
||||
hasInternalDict = true
|
||||
}
|
||||
}
|
||||
if (hasInternalDict)
|
||||
return userDicts to true
|
||||
val internalDicts = DictionaryInfoUtils.getAssetsDictionaryList(context) ?: return userDicts to false
|
||||
val best = LocaleUtils.getBestMatch(locale, internalDicts.toList()) {
|
||||
DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(it)?.constructLocale() ?: SubtypeLocaleUtils.NO_LANGUAGE.constructLocale()
|
||||
}
|
||||
return userDicts to (best != null)
|
||||
}
|
||||
|
||||
// get locales with same script as main locale, but different language
|
||||
private fun getAvailableSecondaryLocales(context: Context, mainLocale: Locale, asciiCapable: Boolean): Set<Locale> {
|
||||
val locales = getDictionaryLocales(context)
|
||||
val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN
|
||||
else mainLocale.script()
|
||||
// script() extension function may return latin in case script cannot be determined
|
||||
// workaround: don't allow secondary locales for these locales
|
||||
if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return emptySet()
|
||||
|
||||
locales.removeAll {
|
||||
it.language == mainLocale.language || it.script() != mainScript
|
||||
}
|
||||
return locales
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import android.widget.Switch
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.Fragment
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.LocaleUtils
|
||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||
import helium314.keyboard.latin.utils.getDictionaryLocales
|
||||
import helium314.keyboard.latin.utils.locale
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
import java.util.*
|
||||
|
||||
// not a SettingsFragment, because with androidx.preferences it's very complicated or
|
||||
// impossible to have the languages RecyclerView scrollable (this way it works nicely out of the box)
|
||||
class LanguageSettingsFragment : Fragment(R.layout.language_settings) {
|
||||
private val sortedSubtypesByDisplayName = LinkedHashMap<String, MutableList<SubtypeInfo>>()
|
||||
private val enabledSubtypes = mutableListOf<InputMethodSubtype>()
|
||||
private val systemLocales = mutableListOf<Locale>()
|
||||
private lateinit var languageFilterList: LanguageFilterList
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var systemOnlySwitch: Switch
|
||||
private val dictionaryLocales by lazy { getDictionaryLocales(requireContext()) }
|
||||
|
||||
private val dictionaryFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
listener?.onNewDictionary(uri)
|
||||
}
|
||||
|
||||
private val layoutFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||
val uri = it.data?.data ?: return@registerForActivityResult
|
||||
listener?.onNewLayoutFile(uri)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
prefs = requireContext().prefs()
|
||||
|
||||
SubtypeLocaleUtils.init(requireContext())
|
||||
|
||||
enabledSubtypes.addAll(SubtypeSettings.getEnabledSubtypes())
|
||||
systemLocales.addAll(SubtypeSettings.getSystemLocales())
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState) ?: return null
|
||||
systemOnlySwitch = view.findViewById(R.id.language_switch)
|
||||
systemOnlySwitch.isChecked = false
|
||||
systemOnlySwitch.setOnCheckedChangeListener { _, b ->
|
||||
enabledSubtypes.clear()
|
||||
enabledSubtypes.addAll(SubtypeSettings.getEnabledSubtypes())
|
||||
loadSubtypes(b)
|
||||
}
|
||||
languageFilterList = LanguageFilterList(view.findViewById(R.id.search_field), view.findViewById(R.id.language_list))
|
||||
loadSubtypes(systemOnlySwitch.isChecked)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
languageFilterList.setSettingsFragment(this)
|
||||
val activity: Activity? = activity
|
||||
if (activity is AppCompatActivity) {
|
||||
val actionBar = activity.supportActionBar ?: return
|
||||
actionBar.setTitle(R.string.language_and_layouts_title)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
languageFilterList.setSettingsFragment(null)
|
||||
}
|
||||
|
||||
private fun loadSubtypes(systemOnly: Boolean) {
|
||||
sortedSubtypesByDisplayName.clear()
|
||||
// list of all subtypes, any subtype added to sortedSubtypes will be removed to avoid duplicates
|
||||
val allSubtypes = SubtypeSettings.getAllAvailableSubtypes().toMutableList()
|
||||
fun List<Locale>.sortedAddToSubtypesAndRemoveFromAllSubtypes() {
|
||||
val subtypesToAdd = mutableListOf<SubtypeInfo>()
|
||||
forEach { locale ->
|
||||
val iterator = allSubtypes.iterator()
|
||||
var added = false
|
||||
while (iterator.hasNext()) {
|
||||
val subtype = iterator.next()
|
||||
if (subtype.locale() == locale) {
|
||||
// add subtypes with matching locale
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(locale))
|
||||
iterator.remove()
|
||||
added = true
|
||||
}
|
||||
}
|
||||
// if locale has a country try again, but match language and script only
|
||||
if (!added && locale.country.isNotEmpty()) {
|
||||
val language = locale.language
|
||||
val script = locale.script()
|
||||
val iter = allSubtypes.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val subtype = iter.next()
|
||||
val subtypeLocale = subtype.locale()
|
||||
if (subtypeLocale.toLanguageTag() == subtypeLocale.language && subtypeLocale.language == language && script == subtypeLocale.script()) {
|
||||
// add subtypes using the language only
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(language.constructLocale()))
|
||||
iter.remove()
|
||||
added = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// try again if script is not the default script, match language only
|
||||
if (!added && locale.script() != locale.language.constructLocale().script()) {
|
||||
val language = locale.language
|
||||
val iter = allSubtypes.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val subtype = iter.next()
|
||||
if (subtype.locale().language == language) {
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(subtype.locale()))
|
||||
iter.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subtypesToAdd.sortedBy { it.displayName }.addToSortedSubtypes()
|
||||
}
|
||||
|
||||
// add enabled subtypes
|
||||
enabledSubtypes.map { it.toSubtypeInfo(it.locale(), true) }
|
||||
.sortedBy { it.displayName }.addToSortedSubtypes()
|
||||
allSubtypes.removeAll(enabledSubtypes)
|
||||
|
||||
if (systemOnly) { // don't add anything else
|
||||
languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly)
|
||||
return
|
||||
}
|
||||
|
||||
// add subtypes that have a dictionary
|
||||
val localesWithDictionary = DictionaryInfoUtils.getCachedDirectoryList(requireContext())?.mapNotNull { dir ->
|
||||
if (!dir.isDirectory)
|
||||
return@mapNotNull null
|
||||
if (dir.list()?.any { it.endsWith(DictionaryInfoUtils.USER_DICTIONARY_SUFFIX) } == true)
|
||||
dir.name.constructLocale()
|
||||
else null
|
||||
}
|
||||
localesWithDictionary?.sortedAddToSubtypesAndRemoveFromAllSubtypes()
|
||||
|
||||
// add subtypes for device locales
|
||||
systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes()
|
||||
|
||||
// add the remaining ones
|
||||
allSubtypes.map { it.toSubtypeInfo(it.locale()) }
|
||||
.sortedBy { if (it.subtype.locale().toLanguageTag().equals(SubtypeLocaleUtils.NO_LANGUAGE, true))
|
||||
SubtypeLocaleUtils.NO_LANGUAGE // "No language (Alphabet)" should be last
|
||||
else it.displayName
|
||||
}.addToSortedSubtypes()
|
||||
|
||||
// set languages
|
||||
languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly)
|
||||
}
|
||||
|
||||
private fun InputMethodSubtype.toSubtypeInfo(locale: Locale, isEnabled: Boolean = false) =
|
||||
toSubtypeInfo(locale, requireContext(), isEnabled, LocaleUtils.getBestMatch(locale, dictionaryLocales) {it} != null)
|
||||
|
||||
private fun List<SubtypeInfo>.addToSortedSubtypes() {
|
||||
forEach {
|
||||
sortedSubtypesByDisplayName.getOrPut(it.displayName) { mutableListOf() }.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onNewDictionary(uri: Uri?)
|
||||
fun onNewLayoutFile(uri: Uri?)
|
||||
}
|
||||
|
||||
private var listener: Listener? = null
|
||||
|
||||
fun setListener(newListener: Listener?) {
|
||||
listener = newListener
|
||||
}
|
||||
|
||||
fun requestDictionary() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
dictionaryFilePicker.launch(intent)
|
||||
}
|
||||
|
||||
fun requestLayoutFile() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
// todo: any working way to allow only json and text files?
|
||||
.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/*", "application/octet-stream", "application/json"))
|
||||
.setType("*/*")
|
||||
layoutFilePicker.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var isEnabled: Boolean, var hasDictionary: Boolean) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is SubtypeInfo) return false
|
||||
return subtype == other.subtype
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return subtype.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
fun InputMethodSubtype.toSubtypeInfo(locale: Locale, context: Context, isEnabled: Boolean, hasDictionary: Boolean): SubtypeInfo =
|
||||
SubtypeInfo(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), this, isEnabled, hasDictionary)
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import static android.preference.PreferenceActivity.EXTRA_NO_HEADERS;
|
||||
import static android.preference.PreferenceActivity.EXTRA_SHOW_FRAGMENT;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import helium314.keyboard.latin.permissions.PermissionsManager;
|
||||
import helium314.keyboard.latin.utils.ActivityThemeUtils;
|
||||
import helium314.keyboard.latin.utils.NewDictionaryAdder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
public final class OldSettingsActivity extends AppCompatActivity
|
||||
implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
private static final String DEFAULT_FRAGMENT = SettingsFragment.class.getName();
|
||||
|
||||
public static final String EXTRA_ENTRY_KEY = "entry";
|
||||
public static final String EXTRA_ENTRY_VALUE_APP_ICON = "app_icon";
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
}
|
||||
final Intent i = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(i.getAction()) && i.getData() != null) {
|
||||
new NewDictionaryAdder(this, null).addDictionary(i.getData(), null);
|
||||
setIntent(new Intent()); // avoid opening again
|
||||
}
|
||||
if (getSupportFragmentManager().getFragments().isEmpty())
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
|
||||
ActivityThemeUtils.setActivityTheme(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
final Intent intent = super.getIntent();
|
||||
final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
|
||||
if (fragment == null) {
|
||||
intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
|
||||
}
|
||||
intent.putExtra(EXTRA_NO_HEADERS, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
PermissionsManager.get(this).onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet;
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher;
|
||||
import helium314.keyboard.latin.AudioAndHapticFeedbackManager;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.RichInputMethodManager;
|
||||
import helium314.keyboard.latin.utils.DialogUtilsKt;
|
||||
import helium314.keyboard.latin.utils.PopupKeysUtilsKt;
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings;
|
||||
import helium314.keyboard.latin.utils.SubtypeUtilsKt;
|
||||
|
||||
import kotlin.collections.ArraysKt;
|
||||
|
||||
public final class PreferencesSettingsFragment extends SubScreenFragment {
|
||||
|
||||
private boolean mReloadKeyboard = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_preferences);
|
||||
|
||||
final Context context = getActivity();
|
||||
|
||||
// When we are called from the Settings application but we are not already running, some
|
||||
// singleton and utility classes may not have been initialized. We have to call
|
||||
// initialization method of these classes here. See {@link LatinIME#onCreate()}.
|
||||
RichInputMethodManager.init(context);
|
||||
|
||||
if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) {
|
||||
removePreference(Settings.PREF_VIBRATE_ON);
|
||||
removePreference(Settings.PREF_VIBRATE_IN_DND_MODE);
|
||||
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS);
|
||||
}
|
||||
|
||||
setupKeypressVibrationDurationSettings();
|
||||
setupKeypressSoundVolumeSettings();
|
||||
setupHistoryRetentionTimeSettings();
|
||||
refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings();
|
||||
setLocalizedNumberRowVisibility();
|
||||
setNumberRowHintsVisibility();
|
||||
findPreference(Settings.PREF_POPUP_KEYS_LABELS_ORDER).setVisible(getSharedPreferences().getBoolean(Settings.PREF_SHOW_HINTS, false));
|
||||
findPreference(Settings.PREF_POPUP_KEYS_ORDER).setOnPreferenceClickListener((pref) -> {
|
||||
DialogUtilsKt.reorderDialog(requireContext(), Settings.PREF_POPUP_KEYS_ORDER,
|
||||
PopupKeysUtilsKt.POPUP_KEYS_ORDER_DEFAULT, R.string.popup_order, (x) -> null);
|
||||
return true;
|
||||
});
|
||||
findPreference(Settings.PREF_POPUP_KEYS_LABELS_ORDER).setOnPreferenceClickListener((pref) -> {
|
||||
DialogUtilsKt.reorderDialog(requireContext(), Settings.PREF_POPUP_KEYS_LABELS_ORDER,
|
||||
PopupKeysUtilsKt.POPUP_KEYS_LABEL_DEFAULT, R.string.hint_source, (x) -> null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings();
|
||||
if (key == null) return;
|
||||
switch (key) {
|
||||
case Settings.PREF_POPUP_KEYS_ORDER, Settings.PREF_SHOW_POPUP_HINTS, Settings.PREF_SHOW_NUMBER_ROW_HINTS,
|
||||
Settings.PREF_POPUP_KEYS_LABELS_ORDER, Settings.PREF_LANGUAGE_SWITCH_KEY,
|
||||
Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, Settings.PREF_REMOVE_REDUNDANT_POPUPS -> mReloadKeyboard = true;
|
||||
case Settings.PREF_SHOW_NUMBER_ROW -> {
|
||||
setNumberRowHintsVisibility();
|
||||
mReloadKeyboard = true;
|
||||
}
|
||||
case Settings.PREF_LOCALIZED_NUMBER_ROW -> KeyboardLayoutSet.onSystemLocaleChanged();
|
||||
case Settings.PREF_SHOW_HINTS -> {
|
||||
findPreference(Settings.PREF_POPUP_KEYS_LABELS_ORDER).setVisible(prefs.getBoolean(Settings.PREF_SHOW_HINTS, false));
|
||||
setNumberRowHintsVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mReloadKeyboard)
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext());
|
||||
mReloadKeyboard = false;
|
||||
}
|
||||
|
||||
private void setLocalizedNumberRowVisibility() {
|
||||
final Preference pref = findPreference(Settings.PREF_LOCALIZED_NUMBER_ROW);
|
||||
if (pref == null) return;
|
||||
// locales that have a number row defined (not good to have it hardcoded, but reading a bunch of files may be noticeably slow)
|
||||
final String[] numberRowLocales = new String[] { "ar", "bn", "fa", "gu", "hi", "kn", "mr", "ne", "ur" };
|
||||
for (final InputMethodSubtype subtype : SubtypeSettings.INSTANCE.getEnabledSubtypes(true)) {
|
||||
if (ArraysKt.any(numberRowLocales, (l) -> l.equals(SubtypeUtilsKt.locale(subtype).getLanguage()))) {
|
||||
pref.setVisible(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pref.setVisible(false);
|
||||
}
|
||||
|
||||
private void setNumberRowHintsVisibility() {
|
||||
var prefs = getSharedPreferences();
|
||||
setPreferenceVisible(Settings.PREF_SHOW_NUMBER_ROW_HINTS, prefs.getBoolean(Settings.PREF_SHOW_HINTS, false)
|
||||
&& prefs.getBoolean(Settings.PREF_SHOW_NUMBER_ROW, false));
|
||||
}
|
||||
|
||||
private void refreshEnablingsOfKeypressSoundAndVibrationAndHistRetentionSettings() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
setPreferenceVisible(Settings.PREF_VIBRATION_DURATION_SETTINGS,
|
||||
Settings.readVibrationEnabled(prefs));
|
||||
setPreferenceVisible(Settings.PREF_VIBRATE_IN_DND_MODE,
|
||||
Settings.readVibrationEnabled(prefs));
|
||||
setPreferenceVisible(Settings.PREF_KEYPRESS_SOUND_VOLUME,
|
||||
prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON));
|
||||
setPreferenceVisible(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME,
|
||||
prefs.getBoolean(Settings.PREF_ENABLE_CLIPBOARD_HISTORY, Defaults.PREF_ENABLE_CLIPBOARD_HISTORY));
|
||||
}
|
||||
|
||||
private void setupKeypressVibrationDurationSettings() {
|
||||
final SeekBarDialogPreference pref = findPreference(
|
||||
Settings.PREF_VIBRATION_DURATION_SETTINGS);
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, Defaults.PREF_VIBRATION_DURATION_SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {
|
||||
AudioAndHapticFeedbackManager.getInstance().vibrate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
if (value < 0) {
|
||||
return res.getString(R.string.settings_system_default);
|
||||
}
|
||||
return res.getString(R.string.abbreviation_unit_milliseconds, Integer.toString(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupKeypressSoundVolumeSettings() {
|
||||
final SeekBarDialogPreference pref = findPreference(
|
||||
Settings.PREF_KEYPRESS_SOUND_VOLUME);
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
final AudioManager am = (AudioManager) requireContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
private static final float PERCENTAGE_FLOAT = 100.0f;
|
||||
|
||||
private float getValueFromPercentage(final int percentage) {
|
||||
return percentage / PERCENTAGE_FLOAT;
|
||||
}
|
||||
|
||||
private int getPercentageFromValue(final float floatValue) {
|
||||
return (int)(floatValue * PERCENTAGE_FLOAT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putFloat(key, getValueFromPercentage(value)).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return getPercentageFromValue(prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, Defaults.PREF_KEYPRESS_SOUND_VOLUME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return getPercentageFromValue(-1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
if (value < 0) {
|
||||
return res.getString(R.string.settings_system_default);
|
||||
}
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {
|
||||
am.playSoundEffect(
|
||||
AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupHistoryRetentionTimeSettings() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
final SeekBarDialogPreference pref = findPreference(
|
||||
Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME);
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return prefs.getInt(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME, Defaults.PREF_CLIPBOARD_HISTORY_RETENTION_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return Settings.readDefaultClipboardHistoryRetentionTime(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
if (value <= 0) {
|
||||
return res.getString(R.string.settings_no_limit);
|
||||
}
|
||||
return res.getString(R.string.abbreviation_unit_minutes, Integer.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
|
||||
public final class SeekBarDialogPreference extends Preference
|
||||
implements SeekBar.OnSeekBarChangeListener, DialogInterface.OnClickListener {
|
||||
public interface ValueProxy {
|
||||
int readValue(final String key);
|
||||
int readDefaultValue(final String key);
|
||||
void writeValue(final int value, final String key);
|
||||
void writeDefaultValue(final String key);
|
||||
String getValueText(final int value);
|
||||
void feedbackValue(final int value);
|
||||
}
|
||||
|
||||
private final int mMaxValue;
|
||||
private final int mMinValue;
|
||||
private final int mStepValue;
|
||||
|
||||
private TextView mValueView;
|
||||
private SeekBar mSeekBar;
|
||||
|
||||
private ValueProxy mValueProxy;
|
||||
|
||||
public SeekBarDialogPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.SeekBarDialogPreference, 0, 0);
|
||||
mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
|
||||
mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0);
|
||||
mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setInterface(final ValueProxy proxy) {
|
||||
mValueProxy = proxy;
|
||||
final int value = mValueProxy.readValue(getKey());
|
||||
setSummary(mValueProxy.getValueText(value));
|
||||
}
|
||||
|
||||
private int getProgressFromValue(final int value) {
|
||||
return value - mMinValue;
|
||||
}
|
||||
|
||||
private int getValueFromProgress(final int progress) {
|
||||
return progress + mMinValue;
|
||||
}
|
||||
|
||||
private int clipValue(final int value) {
|
||||
final int clippedValue = Math.min(mMaxValue, Math.max(mMinValue, value));
|
||||
if (mStepValue <= 1) {
|
||||
return clippedValue;
|
||||
}
|
||||
return clippedValue - (clippedValue % mStepValue);
|
||||
}
|
||||
|
||||
private int getClippedValueFromProgress(final int progress) {
|
||||
return clipValue(getValueFromProgress(progress));
|
||||
}
|
||||
|
||||
private void onCreateDialogView(final View view) {
|
||||
mSeekBar = view.findViewById(R.id.seek_bar_dialog_bar);
|
||||
mSeekBar.setMax(mMaxValue - mMinValue);
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
mValueView = view.findViewById(R.id.seek_bar_dialog_value);
|
||||
final int value = mValueProxy.readValue(getKey());
|
||||
mValueView.setText(mValueProxy.getValueText(value));
|
||||
mSeekBar.setProgress(getProgressFromValue(clipValue(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.seek_bar_dialog, null);
|
||||
final AlertDialog dialog = new AlertDialog.Builder(getContext())
|
||||
.setTitle(getTitle())
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, this)
|
||||
.setNeutralButton(R.string.button_default, this)
|
||||
.create();
|
||||
dialog.setOnShowListener((d) -> onCreateDialogView(view));
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, final int which) {
|
||||
final String key = getKey();
|
||||
if (which == DialogInterface.BUTTON_NEUTRAL) {
|
||||
final int value = mValueProxy.readDefaultValue(key);
|
||||
setSummary(mValueProxy.getValueText(value));
|
||||
mValueProxy.writeDefaultValue(key);
|
||||
return;
|
||||
}
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
final int value = getClippedValueFromProgress(mSeekBar.getProgress());
|
||||
setSummary(mValueProxy.getValueText(value));
|
||||
mValueProxy.writeValue(value, key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(final SeekBar seekBar, final int progress,
|
||||
final boolean fromUser) {
|
||||
final int value = getClippedValueFromProgress(progress);
|
||||
mValueView.setText(mValueProxy.getValueText(value));
|
||||
if (!fromUser) {
|
||||
mSeekBar.setProgress(getProgressFromValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(final SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
mValueProxy.feedbackValue(getClippedValueFromProgress(seekBar.getProgress()));
|
||||
}
|
||||
}
|
|
@ -46,9 +46,6 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
|
||||
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = Settings.class.getSimpleName();
|
||||
// Settings screens
|
||||
public static final String SCREEN_DEBUG = "screen_debug";
|
||||
public static final String SCREEN_GESTURE = "screen_gesture";
|
||||
|
||||
// theme-related stuff
|
||||
public static final String PREF_THEME_STYLE = "theme_style";
|
||||
|
@ -72,7 +69,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_POPUP_ON = "popup_on";
|
||||
public static final String PREF_AUTO_CORRECTION = "auto_correction";
|
||||
public static final String PREF_MORE_AUTO_CORRECTION = "more_auto_correction";
|
||||
public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "auto_correction_confidence";
|
||||
public static final String PREF_AUTO_CORRECT_THRESHOLD = "auto_correct_threshold";
|
||||
public static final String PREF_AUTOCORRECT_SHORTCUTS = "autocorrect_shortcuts";
|
||||
public static final String PREF_CENTER_SUGGESTION_TEXT_TO_ENTER = "center_suggestion_text_to_enter";
|
||||
public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
|
||||
|
@ -290,11 +287,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
mPrefs.edit().putBoolean(Settings.PREF_AUTO_CORRECTION, !oldValue).apply();
|
||||
}
|
||||
|
||||
public static String readAutoCorrectConfidence(final SharedPreferences prefs, final Resources res) {
|
||||
return prefs.getString(PREF_AUTO_CORRECTION_CONFIDENCE,
|
||||
res.getString(R.string.auto_correction_threshold_mode_index_modest));
|
||||
}
|
||||
|
||||
public static boolean readGestureDynamicPreviewEnabled(final SharedPreferences prefs) {
|
||||
final boolean followSystem = prefs.getBoolean(PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM, Defaults.PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM);
|
||||
final boolean defValue = Defaults.PREF_GESTURE_DYNAMIC_PREVIEW_FOLLOW_SYSTEM;
|
||||
|
@ -324,10 +316,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply();
|
||||
}
|
||||
|
||||
public static int readDefaultClipboardHistoryRetentionTime(final Resources res) {
|
||||
return res.getInteger(R.integer.config_clipboard_history_retention_time);
|
||||
}
|
||||
|
||||
public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) {
|
||||
return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, Defaults.PREF_SPACE_HORIZONTAL_SWIPE)) {
|
||||
case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR;
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import helium314.keyboard.latin.BuildConfig;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.common.FileUtils;
|
||||
import helium314.keyboard.latin.define.DebugFlags;
|
||||
import helium314.keyboard.latin.utils.DictionaryUtilsKt;
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils;
|
||||
import helium314.keyboard.latin.utils.JniUtils;
|
||||
import helium314.keyboard.latin.utils.KtxKt;
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings;
|
||||
import helium314.keyboard.latin.utils.SubtypeUtilsKt;
|
||||
|
||||
import java.util.List;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public final class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private final ArrayList<File> crashReportFiles = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle bundle, @Nullable String s) {
|
||||
addPreferencesFromResource(R.xml.prefs);
|
||||
if (!JniUtils.sHaveGestureLib) {
|
||||
final Preference gesturePreference = findPreference(Settings.SCREEN_GESTURE);
|
||||
getPreferenceScreen().removePreference(gesturePreference);
|
||||
}
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD)
|
||||
.execute(() -> DictionaryUtilsKt.cleanUnusedMainDicts(requireContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final Activity activity = getActivity();
|
||||
if (activity instanceof AppCompatActivity) {
|
||||
final ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar();
|
||||
final CharSequence screenTitle = getPreferenceScreen().getTitle();
|
||||
if (actionBar != null && screenTitle != null) {
|
||||
actionBar.setTitle(screenTitle);
|
||||
}
|
||||
}
|
||||
|
||||
findPreference("screen_languages").setSummary(getEnabledSubtypesLabel());
|
||||
if (BuildConfig.DEBUG || DebugFlags.DEBUG_ENABLED)
|
||||
askAboutCrashReports();
|
||||
}
|
||||
|
||||
private String getEnabledSubtypesLabel() {
|
||||
final List<InputMethodSubtype> subtypes = SubtypeSettings.INSTANCE.getEnabledSubtypes(true);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final InputMethodSubtype subtype : subtypes) {
|
||||
if (sb.length() > 0)
|
||||
sb.append(", ");
|
||||
sb.append(SubtypeUtilsKt.displayName(subtype, requireContext()));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void askAboutCrashReports() {
|
||||
// find crash report files
|
||||
final File dir = requireContext().getExternalFilesDir(null);
|
||||
if (dir == null) return;
|
||||
final File[] allFiles = dir.listFiles();
|
||||
if (allFiles == null) return;
|
||||
crashReportFiles.clear();
|
||||
for (File file : allFiles) {
|
||||
if (file.getName().startsWith("crash_report"))
|
||||
crashReportFiles.add(file);
|
||||
}
|
||||
if (crashReportFiles.isEmpty()) return;
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage("Crash report files found")
|
||||
.setPositiveButton("get", (dialogInterface, i) -> {
|
||||
final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.putExtra(Intent.EXTRA_TITLE, "crash_reports.zip");
|
||||
intent.setType("application/zip");
|
||||
crashReportFilePicker.launch(intent);
|
||||
})
|
||||
.setNeutralButton("delete", (dialogInterface, i) -> {
|
||||
for (File file : crashReportFiles) {
|
||||
file.delete(); // don't care whether it fails, though user will complain
|
||||
}
|
||||
})
|
||||
.setNegativeButton("ignore", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
final ActivityResultLauncher<Intent> crashReportFilePicker = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (intent) -> {
|
||||
if (intent.getResultCode() != Activity.RESULT_OK || intent.getData() == null) return;
|
||||
final Uri uri = intent.getData().getData();
|
||||
if (uri != null)
|
||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> saveCrashReport(uri));
|
||||
});
|
||||
|
||||
private void saveCrashReport(final Uri uri) {
|
||||
if (uri == null || crashReportFiles.isEmpty()) return;
|
||||
final OutputStream os;
|
||||
try {
|
||||
os = requireContext().getContentResolver().openOutputStream(uri);
|
||||
if (os == null) return;
|
||||
final BufferedOutputStream bos = new BufferedOutputStream(os);
|
||||
final ZipOutputStream z = new ZipOutputStream(bos);
|
||||
for (File file : crashReportFiles) {
|
||||
FileInputStream f = new FileInputStream(file);
|
||||
z.putNextEntry(new ZipEntry(file.getName()));
|
||||
FileUtils.copyStreamToOtherStream(f, z);
|
||||
f.close();
|
||||
z.closeEntry();
|
||||
}
|
||||
z.close();
|
||||
bos.close();
|
||||
os.close();
|
||||
for (File file : crashReportFiles) {
|
||||
file.delete();
|
||||
}
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
|
@ -28,12 +28,10 @@ import helium314.keyboard.latin.common.Colors;
|
|||
import helium314.keyboard.latin.permissions.PermissionsUtil;
|
||||
import helium314.keyboard.latin.utils.InputTypeUtils;
|
||||
import helium314.keyboard.latin.utils.JniUtils;
|
||||
import helium314.keyboard.latin.utils.Log;
|
||||
import helium314.keyboard.latin.utils.ScriptUtils;
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings;
|
||||
import helium314.keyboard.latin.utils.SubtypeUtilsKt;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -43,13 +41,7 @@ import java.util.Locale;
|
|||
*/
|
||||
// Non-final for testing via mock library.
|
||||
public class SettingsValues {
|
||||
private static final String TAG = SettingsValues.class.getSimpleName();
|
||||
// "floatMaxValue" and "floatNegativeInfinity" are special marker strings for
|
||||
// Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings.
|
||||
private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue";
|
||||
private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity";
|
||||
public static final float DEFAULT_SIZE_SCALE = 1.0f; // 100%
|
||||
public static final float AUTO_CORRECTION_DISABLED_THRESHOLD = Float.MAX_VALUE;
|
||||
|
||||
// From resources:
|
||||
public final SpacingAndPunctuations mSpacingAndPunctuations;
|
||||
|
@ -194,8 +186,8 @@ public class SettingsValues {
|
|||
&& (mUrlDetectionEnabled || !InputTypeUtils.isUriOrEmailType(mInputAttributes.mInputType));
|
||||
mCenterSuggestionTextToEnter = prefs.getBoolean(Settings.PREF_CENTER_SUGGESTION_TEXT_TO_ENTER, Defaults.PREF_CENTER_SUGGESTION_TEXT_TO_ENTER);
|
||||
mAutoCorrectionThreshold = mAutoCorrectEnabled
|
||||
? readAutoCorrectionThreshold(res, prefs)
|
||||
: AUTO_CORRECTION_DISABLED_THRESHOLD;
|
||||
? prefs.getFloat(Settings.PREF_AUTO_CORRECT_THRESHOLD, Defaults.PREF_AUTO_CORRECT_THRESHOLD)
|
||||
: Float.MAX_VALUE;
|
||||
mScoreLimitForAutocorrect = (mAutoCorrectionThreshold < 0) ? 600000 // very aggressive
|
||||
: (mAutoCorrectionThreshold < 0.07 ? 800000 : 950000); // aggressive or modest
|
||||
mAutoCorrectShortcuts = prefs.getBoolean(Settings.PREF_AUTOCORRECT_SHORTCUTS, Defaults.PREF_AUTOCORRECT_SHORTCUTS);
|
||||
|
@ -342,39 +334,6 @@ public class SettingsValues {
|
|||
return mDisplayOrientation == configuration.orientation;
|
||||
}
|
||||
|
||||
// todo: way too complicated
|
||||
private static float readAutoCorrectionThreshold(final Resources res,
|
||||
final SharedPreferences prefs) {
|
||||
final String currentAutoCorrectionSetting = Settings.readAutoCorrectConfidence(prefs, res);
|
||||
final String[] autoCorrectionThresholdValues = res.getStringArray(
|
||||
R.array.auto_correction_threshold_values);
|
||||
// When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
|
||||
final float autoCorrectionThreshold;
|
||||
try {
|
||||
final int arrayIndex = Integer.parseInt(currentAutoCorrectionSetting);
|
||||
if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
|
||||
final String val = autoCorrectionThresholdValues[arrayIndex];
|
||||
if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) {
|
||||
autoCorrectionThreshold = Float.MAX_VALUE;
|
||||
} else if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) {
|
||||
autoCorrectionThreshold = Float.NEGATIVE_INFINITY;
|
||||
} else {
|
||||
autoCorrectionThreshold = Float.parseFloat(val);
|
||||
}
|
||||
} else {
|
||||
autoCorrectionThreshold = Float.MAX_VALUE;
|
||||
}
|
||||
} catch (final NumberFormatException e) {
|
||||
// Whenever the threshold settings are correct, never come here.
|
||||
Log.w(TAG, "Cannot load auto correction threshold setting."
|
||||
+ " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
|
||||
+ ", autoCorrectionThresholdValues: "
|
||||
+ Arrays.toString(autoCorrectionThresholdValues), e);
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
return autoCorrectionThreshold;
|
||||
}
|
||||
|
||||
private static boolean readUseContactsEnabled(final SharedPreferences prefs, final Context context) {
|
||||
final boolean setting = prefs.getBoolean(Settings.PREF_USE_CONTACTS, Defaults.PREF_USE_CONTACTS);
|
||||
if (!setting) return false;
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import helium314.keyboard.latin.utils.Log;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
/**
|
||||
* A base abstract class for a {@link PreferenceFragmentCompat} that implements a nested
|
||||
* {@link PreferenceScreen} of the main preference screen.
|
||||
*/
|
||||
public abstract class SubScreenFragment extends PreferenceFragmentCompat
|
||||
implements OnSharedPreferenceChangeListener {
|
||||
private OnSharedPreferenceChangeListener mSharedPreferenceChangeListener;
|
||||
|
||||
static void setPreferenceVisible(final String prefKey, final boolean visible,
|
||||
final PreferenceScreen screen) {
|
||||
final Preference preference = screen.findPreference(prefKey);
|
||||
if (preference != null) {
|
||||
preference.setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
static void removePreference(final String prefKey, final PreferenceScreen screen) {
|
||||
final Preference preference = screen.findPreference(prefKey);
|
||||
if (preference != null && !screen.removePreference(preference)) {
|
||||
final int count = screen.getPreferenceCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Preference pref = screen.getPreference(i);
|
||||
if (pref instanceof PreferenceCategory
|
||||
&& ((PreferenceCategory) pref).removePreference(preference))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final void setPreferenceVisible(final String prefKey, final boolean visible) {
|
||||
setPreferenceVisible(prefKey, visible, getPreferenceScreen());
|
||||
}
|
||||
|
||||
final void removePreference(final String prefKey) {
|
||||
removePreference(prefKey, getPreferenceScreen());
|
||||
}
|
||||
|
||||
final SharedPreferences getSharedPreferences() {
|
||||
return getPreferenceManager().getSharedPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String s) {
|
||||
// this must be overridden, but is useless, because it's called during onCreate
|
||||
// so there is no possibility of calling setStorageDeviceProtected before this is called...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
getPreferenceManager().setStorageDeviceProtected();
|
||||
}
|
||||
mSharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
final SubScreenFragment fragment = SubScreenFragment.this;
|
||||
final Context context = fragment.getActivity();
|
||||
if (context == null || fragment.getPreferenceScreen() == null) {
|
||||
final String tag = fragment.getClass().getSimpleName();
|
||||
// TODO: Introduce a static function to register this class and ensure that
|
||||
// onCreate must be called before "onSharedPreferenceChanged" is called.
|
||||
Log.w(tag, "onSharedPreferenceChanged called before activity starts.");
|
||||
return;
|
||||
}
|
||||
new BackupManager(context).dataChanged();
|
||||
fragment.onSharedPreferenceChanged(prefs, key);
|
||||
}
|
||||
};
|
||||
getSharedPreferences().registerOnSharedPreferenceChangeListener(
|
||||
mSharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final Activity activity = getActivity();
|
||||
if (activity instanceof AppCompatActivity) {
|
||||
final ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar();
|
||||
final PreferenceScreen ps = getPreferenceScreen();
|
||||
if (ps == null) return;
|
||||
final CharSequence screenTitle = ps.getTitle();
|
||||
if (actionBar != null && screenTitle != null) {
|
||||
actionBar.setTitle(screenTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
|
||||
mSharedPreferenceChangeListener);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
// This method may be overridden by an extended class.
|
||||
}
|
||||
|
||||
// for fixing the indent appearing with androidx preferences
|
||||
// we don't have icons in subscreens, so we don't want indent
|
||||
// should also be possible in xml, but didn't find a way to have it in a theme/style
|
||||
@Override
|
||||
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
|
||||
super.setPreferenceScreen(preferenceScreen);
|
||||
if (preferenceScreen == null) return;
|
||||
int count = preferenceScreen.getPreferenceCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Preference pref = preferenceScreen.getPreference(i);
|
||||
pref.setIconSpaceReserved(false);
|
||||
if (pref instanceof PreferenceCategory) {
|
||||
final int subPrefCount = ((PreferenceCategory) pref).getPreferenceCount();
|
||||
for (int j = 0; j < subPrefCount; j++) {
|
||||
((PreferenceCategory) pref).getPreference(j).setIconSpaceReserved(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package helium314.keyboard.latin.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.utils.defaultClipboardToolbarPref
|
||||
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
||||
import helium314.keyboard.latin.utils.defaultToolbarPref
|
||||
import helium314.keyboard.latin.utils.reorderDialog
|
||||
|
||||
class ToolbarSettingsFragment : SubScreenFragment() {
|
||||
private var reloadKeyboard = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val iconsSet = KeyboardIconsSet.instance
|
||||
iconsSet.loadIcons(requireContext())
|
||||
addPreferencesFromResource(R.xml.prefs_screen_toolbar)
|
||||
|
||||
findPreference<Preference>(Settings.PREF_TOOLBAR_KEYS)?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
reorderDialog(
|
||||
requireContext(), Settings.PREF_TOOLBAR_KEYS, defaultToolbarPref,
|
||||
R.string.toolbar_keys
|
||||
) { iconsSet.getNewDrawable(it, requireContext()) }
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(Settings.PREF_PINNED_TOOLBAR_KEYS)?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
reorderDialog(
|
||||
requireContext(), Settings.PREF_PINNED_TOOLBAR_KEYS, defaultPinnedToolbarPref,
|
||||
R.string.pinned_toolbar_keys
|
||||
) { iconsSet.getNewDrawable(it, requireContext()) }
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(Settings.PREF_CLIPBOARD_TOOLBAR_KEYS)?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
reorderDialog(
|
||||
requireContext(), Settings.PREF_CLIPBOARD_TOOLBAR_KEYS, defaultClipboardToolbarPref,
|
||||
R.string.clipboard_toolbar_keys
|
||||
) { iconsSet.getNewDrawable(it, requireContext()) }
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("customize_key_codes")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
//toolbarKeysCustomizer(requireContext())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (reloadKeyboard)
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||
reloadKeyboard = false
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
|
||||
if (key == null) return
|
||||
when (key) {
|
||||
Settings.PREF_TOOLBAR_KEYS, Settings.PREF_CLIPBOARD_TOOLBAR_KEYS, Settings.PREF_PINNED_TOOLBAR_KEYS,
|
||||
Settings.PREF_QUICK_PIN_TOOLBAR_KEYS -> reloadKeyboard = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,294 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.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 helium314.keyboard.compat.ConfigurationCompatKt;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
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 Locale mLocale;
|
||||
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);
|
||||
final String extraLocale = args.getString(EXTRA_LOCALE);
|
||||
updateLocale(mContext, extraLocale == null ? null : LocaleUtils.constructLocale(extraLocale));
|
||||
}
|
||||
|
||||
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, mLocale);
|
||||
}
|
||||
|
||||
// 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, @Nullable final Locale locale) {
|
||||
mContext = context;
|
||||
|
||||
mLocale = null == locale
|
||||
? ConfigurationCompatKt.locale(mContext.getResources().getConfiguration())
|
||||
: locale;
|
||||
// The keyboard uses the language layout of the user dictionary
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
mWordEditText.setImeHintLocales(new LocaleList(mLocale));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Locale getLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
|
||||
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, mLocale, resolver);
|
||||
}
|
||||
|
||||
public final int apply(@NonNull final Context context) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final String newWord = mWordEditText.getText().toString();
|
||||
|
||||
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, mLocale, context) && newWord.equals(mOldWord)) {
|
||||
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, mLocale, resolver);
|
||||
} else {
|
||||
mMode = MODE_INSERT;
|
||||
}
|
||||
|
||||
if (mMode == MODE_INSERT && hasWord(newWord, mLocale, context)) {
|
||||
return CODE_ALREADY_PRESENT;
|
||||
}
|
||||
|
||||
if (mMode == MODE_INSERT) {
|
||||
// Delete duplicate when adding or updating new word
|
||||
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, mLocale, resolver);
|
||||
// Update the existing word by adding a new one
|
||||
UserDictionary.Words.addWord(context, newWord, Integer.parseInt(mSavedWeight),
|
||||
mSavedShortcut, TextUtils.isEmpty(mLocale.toString()) ? null : mLocale);
|
||||
|
||||
return CODE_UPDATED;
|
||||
}
|
||||
|
||||
// Delete duplicates
|
||||
UserDictionarySettings.deleteWord(newWord, mLocale, 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, mLocale.equals(UserDictionarySettings.emptyLocale) ? null : mLocale);
|
||||
|
||||
return CODE_WORD_ADDED;
|
||||
}
|
||||
|
||||
public boolean isExistingWord(final Context context) {
|
||||
final String newWord = mWordEditText.getText().toString();
|
||||
if (mMode != MODE_EDIT) {
|
||||
return hasWord(newWord, mLocale, 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 Locale locale, final Context context) {
|
||||
final Cursor cursor;
|
||||
|
||||
if (locale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_AND_ALL_LOCALES_SELECTION,
|
||||
new String[] { word }, null);
|
||||
} else {
|
||||
// requires use of locale string for interaction with Android system
|
||||
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
|
||||
HAS_WORD_PROJECTION, HAS_WORD_AND_LOCALE_SELECTION,
|
||||
new String[] { word, locale.toString()}, 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;
|
||||
private final Locale mLocale;
|
||||
|
||||
public LocaleRenderer(final Context context, @NonNull final Locale locale) {
|
||||
mLocaleString = locale.toString();
|
||||
mLocale = locale;
|
||||
|
||||
if ("".equals(locale.toString())) {
|
||||
mDescription = context.getString(R.string.user_dict_settings_all_languages);
|
||||
} else {
|
||||
mDescription = UserDictionarySettings.getLocaleDisplayName(context, locale);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
// used in ArrayAdapter of spinner in UserDictionaryAddWordFragment
|
||||
public String toString() {
|
||||
return mDescription;
|
||||
}
|
||||
public String getLocaleString() {
|
||||
return mLocaleString;
|
||||
}
|
||||
public Locale getLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void addLocaleDisplayNameToList(final Context context,
|
||||
final ArrayList<LocaleRenderer> list, final Locale 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<Locale> sortedLocales = UserDictionaryListFragment.getSortedDictionaryLocales(context);
|
||||
|
||||
// mLocale is removed from the language list as it will be added to the top of the list
|
||||
sortedLocales.remove(mLocale);
|
||||
// "For all languages" is removed from the language list as it will be added at the end of the list
|
||||
sortedLocales.remove(UserDictionarySettings.emptyLocale);
|
||||
|
||||
// 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, mLocale);
|
||||
|
||||
// Next, add all other languages which will be sorted alphabetically in UserDictionaryAddWordFragment.updateSpinner()
|
||||
for (Locale locale : sortedLocales) {
|
||||
addLocaleDisplayNameToList(context, localesList, locale);
|
||||
}
|
||||
|
||||
// Finally, add "All languages" at the end of the list
|
||||
if (!mLocale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
addLocaleDisplayNameToList(context, localesList, UserDictionarySettings.emptyLocale);
|
||||
}
|
||||
|
||||
return localesList;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.Context;
|
||||
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.graphics.drawable.DrawableKt;
|
||||
import androidx.core.widget.TextViewKt;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.common.LocaleUtils;
|
||||
import helium314.keyboard.latin.settings.UserDictionaryAddWordContents.LocaleRenderer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 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 null;
|
||||
});
|
||||
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_bin, 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;
|
||||
return new BitmapDrawable(getResources(), DrawableKt.toBitmap(drawable,
|
||||
(int) (scale * drawable.getIntrinsicHeight()), (int) (scale * drawable.getIntrinsicWidth()), null));
|
||||
}
|
||||
|
||||
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 localeRenderer = (LocaleRenderer)parent.getItemAtPosition(position);
|
||||
|
||||
mContents.updateLocale(requireContext(), localeRenderer.getLocale());
|
||||
// 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, (localeRenderer1, localeRenderer2) -> {
|
||||
if (!localeRenderer1.getLocale().equals(UserDictionarySettings.emptyLocale) && !localeRenderer2.getLocale().equals(UserDictionarySettings.emptyLocale)) {
|
||||
return localeRenderer1.toString().compareToIgnoreCase(localeRenderer2.toString());
|
||||
} else {
|
||||
return localeRenderer1.getLocaleString().compareToIgnoreCase(localeRenderer2.getLocaleString());
|
||||
}
|
||||
});
|
||||
|
||||
// Set "For all languages" to the end of the list
|
||||
if (!localeRenderer.getLocale().equals(UserDictionarySettings.emptyLocale)) {
|
||||
// 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, localeRenderer);
|
||||
|
||||
// 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(localeRenderer.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;
|
||||
final String localeString = args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE);
|
||||
mContents.updateLocale(requireContext(), localeString == null ? null : LocaleUtils.constructLocale(localeString));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.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 helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.utils.KtxKt;
|
||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils;
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings;
|
||||
import helium314.keyboard.latin.utils.SubtypeUtilsKt;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
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<Locale> sortedLocales = getSortedDictionaryLocales(requireContext());
|
||||
|
||||
// Add preference "for all locales"
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(UserDictionarySettings.emptyLocale));
|
||||
// Add preference for each dictionary locale
|
||||
for (final Locale locale : sortedLocales) {
|
||||
userDictGroup.addPreference(createUserDictionaryPreference(locale));
|
||||
}
|
||||
}
|
||||
|
||||
static TreeSet<Locale> getSortedDictionaryLocales(final Context context) {
|
||||
final SharedPreferences prefs = KtxKt.prefs(context);
|
||||
final TreeSet<Locale> sortedLocales = new TreeSet<>(new LocaleComparator());
|
||||
|
||||
// Add the main language selected in the "Language and Layouts" setting except "No language"
|
||||
for (InputMethodSubtype mainSubtype : SubtypeSettings.INSTANCE.getEnabledSubtypes(true)) {
|
||||
final Locale mainLocale = SubtypeUtilsKt.locale(mainSubtype);
|
||||
if (!mainLocale.toLanguageTag().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
|
||||
sortedLocales.add(mainLocale);
|
||||
}
|
||||
// Secondary language is added only if main language is selected and if system language is not enabled
|
||||
final List<InputMethodSubtype> enabled = SubtypeSettings.INSTANCE.getEnabledSubtypes(false);
|
||||
for (InputMethodSubtype subtype : enabled) {
|
||||
if (SubtypeUtilsKt.locale(subtype).equals(mainLocale))
|
||||
sortedLocales.addAll(SubtypeUtilsKt.getSecondaryLocales(subtype.getExtraValue()));
|
||||
}
|
||||
}
|
||||
|
||||
sortedLocales.addAll(SubtypeSettings.INSTANCE.getSystemLocales());
|
||||
return sortedLocales;
|
||||
}
|
||||
|
||||
private static class LocaleComparator implements Comparator<Locale> {
|
||||
@Override
|
||||
public int compare(Locale locale1, Locale locale2) {
|
||||
return locale1.toLanguageTag().compareToIgnoreCase(locale2.toLanguageTag());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single User Dictionary Preference object, with its parameters set.
|
||||
* @param locale The locale for which this user dictionary is for.
|
||||
* @return The corresponding preference.
|
||||
*/
|
||||
private Preference createUserDictionaryPreference(@NonNull final Locale locale) {
|
||||
final Preference newPref = new Preference(requireContext());
|
||||
|
||||
if (locale.toString().isEmpty()) {
|
||||
newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
|
||||
} else {
|
||||
newPref.setTitle(UserDictionarySettings.getLocaleDisplayName(requireContext(), locale));
|
||||
}
|
||||
if (locale == UserDictionarySettings.emptyLocale) newPref.getExtras().putString("locale", "");
|
||||
else newPref.getExtras().putString("locale", locale.toLanguageTag());
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,348 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.settings;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.UserDictionary;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
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;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.common.LocaleUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class UserDictionarySettings extends ListFragment {
|
||||
static final Locale emptyLocale = new Locale("");
|
||||
private static final String[] QUERY_PROJECTION =
|
||||
{ UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT, UserDictionary.Words.FREQUENCY };
|
||||
|
||||
// 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 = {
|
||||
UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT, UserDictionary.Words.FREQUENCY
|
||||
};
|
||||
|
||||
private static final int[] ADAPTER_TO = {
|
||||
R.id.user_dictionary_item_word, R.id.user_dictionary_item_shortcut
|
||||
};
|
||||
|
||||
// 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_AND_WITH_LOCALE = UserDictionary.Words.WORD + "=? AND "
|
||||
+ UserDictionary.Words.SHORTCUT + "=? AND "
|
||||
+ UserDictionary.Words.FREQUENCY + "=? AND "
|
||||
+ UserDictionary.Words.LOCALE + "=?";
|
||||
|
||||
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;
|
||||
|
||||
protected Locale mLocale;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
actionBar.setTitle(R.string.edit_personal_dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
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
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
final Intent intent = requireActivity().getIntent();
|
||||
final String localeFromIntent = null == intent ? null : intent.getStringExtra("locale");
|
||||
|
||||
final Bundle arguments = getArguments();
|
||||
final String localeFromArguments = null == arguments ? null : arguments.getString("locale");
|
||||
|
||||
final String localeString;
|
||||
if (null != localeFromArguments) {
|
||||
localeString = localeFromArguments;
|
||||
} else localeString = localeFromIntent;
|
||||
mLocale = localeString == null ? null : LocaleUtils.constructLocale(localeString);
|
||||
|
||||
createCursor(mLocale == null ? null : mLocale);
|
||||
TextView emptyView = view.findViewById(android.R.id.empty);
|
||||
emptyView.setText(R.string.user_dict_settings_empty_text);
|
||||
|
||||
final ListView listView = getListView();
|
||||
listView.setAdapter(createAdapter());
|
||||
listView.setFastScrollEnabled(true);
|
||||
listView.setEmptyView(emptyView);
|
||||
|
||||
final ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (actionBar == null) return;
|
||||
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
|
||||
// in UserDictionaryAddWordFragment (update/delete/insert) to be seen when
|
||||
// user goes back to this view.
|
||||
listAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void createCursor(@Nullable final Locale locale) {
|
||||
// locale can be any of:
|
||||
// - An actual locale, for use of Locale#toString()
|
||||
// - The emptyLocale. 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".
|
||||
|
||||
// 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.
|
||||
|
||||
if (emptyLocale.equals(locale)) {
|
||||
// Case-insensitive sort
|
||||
mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
QUERY_SELECTION_ALL_LOCALES, null,
|
||||
"UPPER(" + UserDictionary.Words.WORD + ")");
|
||||
} else {
|
||||
// requires use of locale string for interaction with Android system
|
||||
final String queryLocaleString = null != locale ? locale.toString() : Locale.getDefault().toString();
|
||||
mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
|
||||
QUERY_SELECTION, new String[] { queryLocaleString },
|
||||
"UPPER(" + UserDictionary.Words.WORD + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private ListAdapter createAdapter() {
|
||||
return new MyAdapter(requireContext(), R.layout.user_dictionary_item, mCursor,
|
||||
ADAPTER_FROM, ADAPTER_TO);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
showAddOrEditFragment(word, shortcut, weight);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLocaleDisplayName(Context context, Locale locale) {
|
||||
if (locale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
// 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);
|
||||
}
|
||||
return LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit.
|
||||
* @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 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.equals(emptyLocale) ? "" : mLocale.toLanguageTag());
|
||||
AppCompatActivity activity = (AppCompatActivity) requireActivity();
|
||||
activity.getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, UserDictionaryAddWordFragment.class, args)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private String getWord(final int position) {
|
||||
return getEntry(position, UserDictionary.Words.WORD);
|
||||
}
|
||||
|
||||
private String getShortcut(final int position) {
|
||||
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(column));
|
||||
}
|
||||
|
||||
public static void deleteWordInEditMode(final String word, final String shortcut, final String weight,
|
||||
final Locale locale, final ContentResolver resolver) {
|
||||
if (TextUtils.isEmpty(shortcut)) {
|
||||
if (locale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES,
|
||||
new String[] { word, weight });
|
||||
} else {
|
||||
resolver.delete(
|
||||
// requires use of locale string for interaction with Android system
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE,
|
||||
new String[] { word, weight, locale.toString() });
|
||||
}
|
||||
} else {
|
||||
if (locale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
resolver.delete(
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES,
|
||||
new String[] { word, shortcut, weight });
|
||||
} else {
|
||||
resolver.delete(
|
||||
// requires use of locale string for interaction with Android system
|
||||
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_LOCALE,
|
||||
new String[] { word, shortcut, weight, locale.toString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteWord(final String word, final Locale locale, final ContentResolver resolver) {
|
||||
if (locale.equals(UserDictionarySettings.emptyLocale)) {
|
||||
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.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
private class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
|
||||
private AlphabetIndexer mIndexer;
|
||||
|
||||
private final ViewBinder mViewBinder = (v, c, columnIndex) -> {
|
||||
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) {
|
||||
if (TextUtils.isEmpty(shortcutText)) {
|
||||
((TextView)v).setText(weight);
|
||||
} else {
|
||||
((TextView)v).setText(weightAndShortcut);
|
||||
}
|
||||
v.invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
public MyAdapter(final Context context, final int layout, final Cursor c,
|
||||
final String[] from, final int[] to) {
|
||||
super(context, layout, c, from, to, 0 /* flags */);
|
||||
|
||||
if (null != c) {
|
||||
final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet);
|
||||
final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
|
||||
mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
|
||||
}
|
||||
setViewBinder(mViewBinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionForSection(final int section) {
|
||||
return null == mIndexer ? 0 : mIndexer.getPositionForSection(section);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionForPosition(final int position) {
|
||||
return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getSections() {
|
||||
return null == mIndexer ? null : mIndexer.getSections();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.setup;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils;
|
||||
import helium314.keyboard.settings.SettingsActivity;
|
||||
|
||||
public final class SetupActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Intent intent = new Intent();
|
||||
final InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
|
||||
// if (UncachedInputMethodManagerUtils.isThisImeCurrent(this, imm))
|
||||
intent.setClass(this, SettingsActivity.class);
|
||||
// else intent.setClass(this, SetupWizardActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
public final class SetupStartIndicatorView extends LinearLayout {
|
||||
public SetupStartIndicatorView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setOrientation(HORIZONTAL);
|
||||
LayoutInflater.from(context).inflate(R.layout.setup_start_indicator_label, this);
|
||||
|
||||
final LabelView labelView = findViewById(R.id.setup_start_label);
|
||||
labelView.setIndicatorView(findViewById(R.id.setup_start_indicator));
|
||||
}
|
||||
|
||||
public static final class LabelView extends AppCompatTextView {
|
||||
private View mIndicatorView;
|
||||
|
||||
public LabelView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void setIndicatorView(final View indicatorView) {
|
||||
mIndicatorView = indicatorView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPressed(final boolean pressed) {
|
||||
super.setPressed(pressed);
|
||||
updateIndicatorView(pressed);
|
||||
}
|
||||
|
||||
private void updateIndicatorView(final boolean pressed) {
|
||||
if (mIndicatorView != null) {
|
||||
mIndicatorView.setPressed(pressed);
|
||||
mIndicatorView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class IndicatorView extends View {
|
||||
private final Path mIndicatorPath = new Path();
|
||||
private final Paint mIndicatorPaint = new Paint();
|
||||
private final ColorStateList mIndicatorColor;
|
||||
|
||||
public IndicatorView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mIndicatorColor = AppCompatResources.getColorStateList(context, R.color.setup_step_action_background);
|
||||
mIndicatorPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
final int layoutDirection = getLayoutDirection();
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final float halfHeight = height / 2.0f;
|
||||
final Path path = mIndicatorPath;
|
||||
path.rewind();
|
||||
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
||||
// Left arrow
|
||||
path.moveTo(width, 0.0f);
|
||||
path.lineTo(0.0f, halfHeight);
|
||||
path.lineTo(width, height);
|
||||
} else { // LAYOUT_DIRECTION_LTR
|
||||
// Right arrow
|
||||
path.moveTo(0.0f, 0.0f);
|
||||
path.lineTo(width, halfHeight);
|
||||
path.lineTo(0.0f, height);
|
||||
}
|
||||
path.close();
|
||||
final int[] stateSet = getDrawableState();
|
||||
final int color = mIndicatorColor.getColorForState(stateSet, 0);
|
||||
mIndicatorPaint.setColor(color);
|
||||
canvas.drawPath(path, mIndicatorPaint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
|
||||
public final class SetupStepIndicatorView extends View {
|
||||
private final Path mIndicatorPath = new Path();
|
||||
private final Paint mIndicatorPaint = new Paint();
|
||||
private float mXRatio;
|
||||
|
||||
public SetupStepIndicatorView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background));
|
||||
mIndicatorPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
public void setIndicatorPosition(final int stepPos, final int totalStepNum) {
|
||||
final int layoutDirection = getLayoutDirection();
|
||||
// The indicator position is the center of the partition that is equally divided into
|
||||
// the total step number.
|
||||
final float partionWidth = 1.0f / totalStepNum;
|
||||
final float pos = stepPos * partionWidth + partionWidth / 2.0f;
|
||||
mXRatio = (layoutDirection == View.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
final int xPos = (int)(getWidth() * mXRatio);
|
||||
final int height = getHeight();
|
||||
mIndicatorPath.rewind();
|
||||
mIndicatorPath.moveTo(xPos, 0);
|
||||
mIndicatorPath.lineTo(xPos + height, height);
|
||||
mIndicatorPath.lineTo(xPos - height, height);
|
||||
mIndicatorPath.close();
|
||||
canvas.drawPath(mIndicatorPath, mIndicatorPaint);
|
||||
}
|
||||
}
|
|
@ -1,434 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package helium314.keyboard.latin.setup;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.utils.ActivityThemeUtils;
|
||||
import helium314.keyboard.latin.utils.JniUtils;
|
||||
import helium314.keyboard.latin.utils.LeakGuardHandlerWrapper;
|
||||
import helium314.keyboard.latin.utils.ResourceUtils;
|
||||
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils;
|
||||
import helium314.keyboard.settings.SettingsActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class SetupWizardActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
// For debugging purpose.
|
||||
private static final boolean FORCE_TO_SHOW_WELCOME_SCREEN = false;
|
||||
|
||||
private InputMethodManager mImm;
|
||||
|
||||
private View mSetupWizard;
|
||||
private View mWelcomeScreen;
|
||||
private View mSetupScreen;
|
||||
private View mActionStart;
|
||||
private TextView mActionNext;
|
||||
private TextView mStep1Bullet;
|
||||
private TextView mActionFinish;
|
||||
private SetupStepGroup mSetupStepGroup;
|
||||
private static final String STATE_STEP = "step";
|
||||
private int mStepNumber;
|
||||
private boolean mNeedsToAdjustStepNumberToSystemState;
|
||||
private static final int STEP_WELCOME = 0;
|
||||
private static final int STEP_1 = 1;
|
||||
private static final int STEP_2 = 2;
|
||||
private static final int STEP_3 = 3;
|
||||
private static final int STEP_LAUNCHING_IME_SETTINGS = 4;
|
||||
private static final int STEP_BACK_FROM_IME_SETTINGS = 5;
|
||||
|
||||
private SettingsPoolingHandler mHandler;
|
||||
|
||||
private static final class SettingsPoolingHandler
|
||||
extends LeakGuardHandlerWrapper<SetupWizardActivity> {
|
||||
private static final int MSG_POLLING_IME_SETTINGS = 0;
|
||||
private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
|
||||
|
||||
private final InputMethodManager mImmInHandler;
|
||||
|
||||
public SettingsPoolingHandler(@NonNull final SetupWizardActivity ownerInstance,
|
||||
final InputMethodManager imm) {
|
||||
super(ownerInstance);
|
||||
mImmInHandler = imm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull final Message msg) {
|
||||
final SetupWizardActivity setupWizardActivity = getOwnerInstance();
|
||||
if (setupWizardActivity == null) {
|
||||
return;
|
||||
}
|
||||
if (msg.what == MSG_POLLING_IME_SETTINGS) {
|
||||
if (UncachedInputMethodManagerUtils.isThisImeEnabled(setupWizardActivity,
|
||||
mImmInHandler)) {
|
||||
setupWizardActivity.invokeSetupWizardOfThisIme();
|
||||
return;
|
||||
}
|
||||
startPollingImeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void startPollingImeSettings() {
|
||||
sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
|
||||
IME_SETTINGS_POLLING_INTERVAL);
|
||||
}
|
||||
|
||||
public void cancelPollingImeSettings() {
|
||||
removeMessages(MSG_POLLING_IME_SETTINGS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
getWindow().setStatusBarColor(getResources().getColor(R.color.setup_background));
|
||||
ActivityThemeUtils.setActivityTheme(this);
|
||||
|
||||
mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
|
||||
mHandler = new SettingsPoolingHandler(this, mImm);
|
||||
setContentView(R.layout.setup_wizard);
|
||||
mSetupWizard = findViewById(R.id.setup_wizard);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mStepNumber = determineSetupStepNumberFromLauncher();
|
||||
} else {
|
||||
mStepNumber = savedInstanceState.getInt(STATE_STEP);
|
||||
}
|
||||
|
||||
final String applicationName = getResources().getString(getApplicationInfo().labelRes);
|
||||
mWelcomeScreen = findViewById(R.id.setup_welcome_screen);
|
||||
final TextView welcomeTitle = findViewById(R.id.setup_welcome_title);
|
||||
welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName));
|
||||
|
||||
// disable the "with gesture typing" when no library is available (at this point, this likely means library is in system and this is a system app)
|
||||
if (!JniUtils.sHaveGestureLib)
|
||||
((TextView) findViewById(R.id.setup_welcome_description)).setText("");
|
||||
|
||||
mSetupScreen = findViewById(R.id.setup_steps_screen);
|
||||
final TextView stepsTitle = findViewById(R.id.setup_title);
|
||||
stepsTitle.setText(getString(R.string.setup_steps_title, applicationName));
|
||||
|
||||
final SetupStepIndicatorView indicatorView = findViewById(R.id.setup_step_indicator);
|
||||
mSetupStepGroup = new SetupStepGroup(indicatorView);
|
||||
|
||||
mStep1Bullet = findViewById(R.id.setup_step1_bullet);
|
||||
mStep1Bullet.setOnClickListener(this);
|
||||
final SetupStep step1 = new SetupStep(STEP_1, applicationName,
|
||||
mStep1Bullet, findViewById(R.id.setup_step1),
|
||||
R.string.setup_step1_title, R.string.setup_step1_instruction,
|
||||
R.string.setup_step1_finished_instruction, R.drawable.ic_setup_key,
|
||||
R.string.setup_step1_action);
|
||||
final SettingsPoolingHandler handler = mHandler;
|
||||
step1.setAction(() -> {
|
||||
invokeLanguageAndInputSettings();
|
||||
handler.startPollingImeSettings();
|
||||
});
|
||||
mSetupStepGroup.addStep(step1);
|
||||
|
||||
final SetupStep step2 = new SetupStep(STEP_2, applicationName,
|
||||
findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2),
|
||||
R.string.setup_step2_title, R.string.setup_step2_instruction,
|
||||
0 /* finishedInstruction */, R.drawable.ic_setup_select,
|
||||
R.string.setup_step2_action);
|
||||
step2.setAction(this::invokeInputMethodPicker);
|
||||
mSetupStepGroup.addStep(step2);
|
||||
|
||||
final SetupStep step3 = new SetupStep(STEP_3, applicationName,
|
||||
findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3),
|
||||
R.string.setup_step3_title, R.string.setup_step3_instruction,
|
||||
0 /* finishedInstruction */, R.drawable.sym_keyboard_language_switch,
|
||||
R.string.setup_step3_action);
|
||||
step3.setAction(() -> {
|
||||
final Intent intent = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
mSetupStepGroup.addStep(step3);
|
||||
|
||||
mActionStart = findViewById(R.id.setup_start_label);
|
||||
mActionStart.setOnClickListener(this);
|
||||
|
||||
mActionNext = findViewById(R.id.setup_next);
|
||||
mActionNext.setOnClickListener(this);
|
||||
|
||||
mActionFinish = findViewById(R.id.setup_finish);
|
||||
final Drawable finishDrawable = ContextCompat.getDrawable(this, R.drawable.ic_setup_check);
|
||||
if (finishDrawable == null) {
|
||||
return;
|
||||
}
|
||||
DrawableCompat.setTintList(finishDrawable, step1.mTextColorStateList);
|
||||
mActionFinish.setCompoundDrawablesRelativeWithIntrinsicBounds(finishDrawable, null, null, null);
|
||||
mActionFinish.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (v == mActionFinish) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
final int currentStep = determineSetupStepNumber();
|
||||
final int nextStep;
|
||||
if (v == mActionStart) {
|
||||
nextStep = STEP_1;
|
||||
} else if (v == mActionNext) {
|
||||
nextStep = mStepNumber + 1;
|
||||
} else if (v == mStep1Bullet && currentStep == STEP_2) {
|
||||
nextStep = STEP_1;
|
||||
} else {
|
||||
nextStep = mStepNumber;
|
||||
}
|
||||
if (mStepNumber != nextStep) {
|
||||
mStepNumber = nextStep;
|
||||
updateSetupStepView();
|
||||
}
|
||||
}
|
||||
|
||||
void invokeSetupWizardOfThisIme() {
|
||||
final Intent intent = new Intent();
|
||||
intent.setClass(this, SetupWizardActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
| Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
mNeedsToAdjustStepNumberToSystemState = true;
|
||||
}
|
||||
|
||||
private void invokeSettingsOfThisIme() {
|
||||
final Intent intent = new Intent();
|
||||
intent.setClass(this, SettingsActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
// intent.putExtra(OldSettingsActivity.EXTRA_ENTRY_KEY,
|
||||
// OldSettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
void invokeLanguageAndInputSettings() {
|
||||
final Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
startActivity(intent);
|
||||
mNeedsToAdjustStepNumberToSystemState = true;
|
||||
}
|
||||
|
||||
void invokeInputMethodPicker() {
|
||||
// Invoke input method picker.
|
||||
mImm.showInputMethodPicker();
|
||||
mNeedsToAdjustStepNumberToSystemState = true;
|
||||
}
|
||||
|
||||
private int determineSetupStepNumberFromLauncher() {
|
||||
final int stepNumber = determineSetupStepNumber();
|
||||
if (stepNumber == STEP_1) {
|
||||
return STEP_WELCOME;
|
||||
}
|
||||
if (stepNumber == STEP_3) {
|
||||
return STEP_LAUNCHING_IME_SETTINGS;
|
||||
}
|
||||
return stepNumber;
|
||||
}
|
||||
|
||||
private int determineSetupStepNumber() {
|
||||
mHandler.cancelPollingImeSettings();
|
||||
if (FORCE_TO_SHOW_WELCOME_SCREEN) {
|
||||
return STEP_1;
|
||||
}
|
||||
if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) {
|
||||
return STEP_1;
|
||||
}
|
||||
if (!UncachedInputMethodManagerUtils.isThisImeCurrent(this, mImm)) {
|
||||
return STEP_2;
|
||||
}
|
||||
return STEP_3;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(STATE_STEP, mStepNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
mStepNumber = savedInstanceState.getInt(STATE_STEP);
|
||||
}
|
||||
|
||||
private static boolean isInSetupSteps(final int stepNumber) {
|
||||
return stepNumber >= STEP_1 && stepNumber <= STEP_3;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestart() {
|
||||
super.onRestart();
|
||||
// Probably the setup wizard has been invoked from "Recent" menu. The setup step number
|
||||
// needs to be adjusted to system state, because the state (IME is enabled and/or current)
|
||||
// may have been changed.
|
||||
if (isInSetupSteps(mStepNumber)) {
|
||||
mStepNumber = determineSetupStepNumber();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) {
|
||||
// Prevent white screen flashing while launching settings activity.
|
||||
mSetupWizard.setVisibility(View.INVISIBLE);
|
||||
invokeSettingsOfThisIme();
|
||||
mStepNumber = STEP_BACK_FROM_IME_SETTINGS;
|
||||
return;
|
||||
}
|
||||
if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
updateSetupStepView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mStepNumber == STEP_1) {
|
||||
mStepNumber = STEP_WELCOME;
|
||||
updateSetupStepView();
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(final boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && mNeedsToAdjustStepNumberToSystemState) {
|
||||
mNeedsToAdjustStepNumberToSystemState = false;
|
||||
mStepNumber = determineSetupStepNumber();
|
||||
updateSetupStepView();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSetupStepView() {
|
||||
mSetupWizard.setVisibility(View.VISIBLE);
|
||||
final boolean welcomeScreen = (mStepNumber == STEP_WELCOME);
|
||||
mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE);
|
||||
mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE);
|
||||
if (welcomeScreen) {
|
||||
return;
|
||||
}
|
||||
final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber();
|
||||
mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone);
|
||||
mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE);
|
||||
mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
static final class SetupStep implements View.OnClickListener {
|
||||
public final int mStepNo;
|
||||
private final View mStepView;
|
||||
private final TextView mBulletView;
|
||||
private final ColorStateList mTextColorStateList;
|
||||
private final String mInstruction;
|
||||
private final String mFinishedInstruction;
|
||||
private final TextView mActionLabel;
|
||||
private Runnable mAction;
|
||||
|
||||
public SetupStep(final int stepNo, final String applicationName, final TextView bulletView,
|
||||
final View stepView, final int title, final int instruction,
|
||||
final int finishedInstruction, final int actionIcon, final int actionLabel) {
|
||||
mStepNo = stepNo;
|
||||
mStepView = stepView;
|
||||
mBulletView = bulletView;
|
||||
final Resources res = stepView.getResources();
|
||||
mTextColorStateList = AppCompatResources.getColorStateList(mStepView.getContext(), R.color.setup_step_action_text);
|
||||
|
||||
final TextView titleView = mStepView.findViewById(R.id.setup_step_title);
|
||||
titleView.setText(res.getString(title, applicationName));
|
||||
|
||||
mInstruction = (instruction == 0) ? null : res.getString(instruction, applicationName);
|
||||
mFinishedInstruction = (finishedInstruction == 0) ? null : res.getString(finishedInstruction, applicationName);
|
||||
|
||||
mActionLabel = mStepView.findViewById(R.id.setup_step_action_label);
|
||||
mActionLabel.setText(res.getString(actionLabel));
|
||||
final Drawable actionIconDrawable = ResourcesCompat.getDrawable(res, actionIcon, null);
|
||||
if (actionIconDrawable == null) {
|
||||
return;
|
||||
}
|
||||
DrawableCompat.setTintList(actionIconDrawable, mTextColorStateList);
|
||||
if (actionIcon == 0) {
|
||||
final int paddingEnd = mActionLabel.getPaddingEnd();
|
||||
mActionLabel.setPaddingRelative(paddingEnd, 0, paddingEnd, 0);
|
||||
} else {
|
||||
final int size = ResourceUtils.toPx(24, res);
|
||||
actionIconDrawable.setBounds(0,0, size, size);
|
||||
mActionLabel.setCompoundDrawablesRelative(actionIconDrawable, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) {
|
||||
mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
||||
mBulletView.setTextColor(enabled
|
||||
? mBulletView.getContext().getResources().getColor(R.color.setup_step_action_text_pressed)
|
||||
: mBulletView.getContext().getResources().getColor(R.color.setup_text_action));
|
||||
final TextView instructionView = mStepView.findViewById(R.id.setup_step_instruction);
|
||||
instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction);
|
||||
mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void setAction(final Runnable action) {
|
||||
mActionLabel.setOnClickListener(this);
|
||||
mAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (v == mActionLabel && mAction != null)
|
||||
mAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
static final class SetupStepGroup {
|
||||
private final SetupStepIndicatorView mIndicatorView;
|
||||
private final ArrayList<SetupStep> mGroup = new ArrayList<>();
|
||||
|
||||
public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
|
||||
mIndicatorView = indicatorView;
|
||||
}
|
||||
|
||||
public void addStep(final SetupStep step) {
|
||||
mGroup.add(step);
|
||||
}
|
||||
|
||||
public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) {
|
||||
for (final SetupStep step : mGroup) {
|
||||
step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone);
|
||||
}
|
||||
mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import helium314.keyboard.latin.utils.SubtypeSettings;
|
|||
import helium314.keyboard.latin.utils.SuggestionResults;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -394,7 +395,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
|||
return new Result(null /* gatheredSuggestions */,
|
||||
false /* hasRecommendedSuggestions */);
|
||||
}
|
||||
final ArrayList<String> suggestions = new ArrayList<>();
|
||||
final LinkedHashSet<String> suggestionsSet = new LinkedHashSet<>();
|
||||
for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
|
||||
final String suggestion;
|
||||
if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
|
||||
|
@ -405,15 +406,15 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
|||
} else {
|
||||
suggestion = suggestedWordInfo.mWord;
|
||||
}
|
||||
suggestions.add(suggestion);
|
||||
suggestionsSet.add(suggestion);
|
||||
}
|
||||
StringUtils.removeDupes(suggestions);
|
||||
final ArrayList<String> suggestions = new ArrayList<>(suggestionsSet);
|
||||
// This returns a String[], while toArray() returns an Object[] which cannot be cast
|
||||
// into a String[].
|
||||
final List<String> gatheredSuggestionsList =
|
||||
suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit));
|
||||
final String[] gatheredSuggestions =
|
||||
gatheredSuggestionsList.toArray(new String[gatheredSuggestionsList.size()]);
|
||||
gatheredSuggestionsList.toArray(new String[0]);
|
||||
|
||||
final int bestScore = suggestionResults.first().mScore;
|
||||
final String bestSuggestion = suggestions.get(0);
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.latin.spellcheck
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import helium314.keyboard.settings.SettingsActivity
|
||||
|
||||
// the Settings in SettingsContainer expect to be in a SettingsActivity, so we use a simple way of getting there
|
||||
class SpellCheckerSettingsActivity : ComponentActivity() {
|
||||
class SpellCheckerSettingsActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val intent = Intent()
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.latin.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
public class ActivityThemeUtils {
|
||||
|
||||
public static void setActivityTheme(final Activity activity) {
|
||||
final boolean isNight = ResourceUtils.isNight(activity.getResources());
|
||||
|
||||
// Set the icons of the status bar and the navigation bar light or dark
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
final WindowInsetsController controller = activity.getWindow().getInsetsController();
|
||||
if (controller != null && !isNight) {
|
||||
controller.setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
|
||||
controller.setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
final View view = activity.getWindow().getDecorView();
|
||||
view.setSystemUiVisibility(!isNight ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -36,18 +36,4 @@ public final class DebugLogUtils {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stack trace contained in an exception as a human-readable string.
|
||||
* @param t the throwable
|
||||
* @return the human-readable stack trace
|
||||
*/
|
||||
public static String getStackTrace(final Throwable t) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final StackTraceElement[] frames = t.getStackTrace();
|
||||
for (int j = 0; j < frames.length; ++j) {
|
||||
sb.append(frames[j].toString() + "\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class DeviceProtectedUtils {
|
||||
|
@ -23,11 +21,11 @@ public final class DeviceProtectedUtils {
|
|||
if (prefs != null)
|
||||
return prefs;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs = getDefaultSharedPreferences(context);
|
||||
return prefs;
|
||||
}
|
||||
final Context deviceProtectedContext = getDeviceProtectedContext(context);
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(deviceProtectedContext);
|
||||
prefs = getDefaultSharedPreferences(deviceProtectedContext);
|
||||
if (prefs.getAll() == null)
|
||||
return prefs; // happens for compose previews
|
||||
if (prefs.getAll().isEmpty()) {
|
||||
|
@ -45,6 +43,11 @@ public final class DeviceProtectedUtils {
|
|||
else return ctx;
|
||||
}
|
||||
|
||||
private static SharedPreferences getDefaultSharedPreferences(Context context) {
|
||||
// from androidx.preference.PreferenceManager
|
||||
return context.getSharedPreferences(context.getPackageName() + "_preferences", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static File getFilesDir(final Context context) {
|
||||
return getDeviceProtectedContext(context).getFilesDir();
|
||||
}
|
||||
|
|
|
@ -1,136 +1,12 @@
|
|||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import helium314.keyboard.latin.R
|
||||
import java.util.Collections
|
||||
|
||||
// todo: ideally the custom InputMethodPicker would be removed / replaced with compose dialog, then this can be removed
|
||||
fun getPlatformDialogThemeContext(context: Context): Context {
|
||||
// Because {@link AlertDialog.Builder.create()} doesn't honor the specified theme with
|
||||
// createThemeContextWrapper=false, the result dialog box has unneeded paddings around it.
|
||||
return ContextThemeWrapper(context, R.style.platformActivityTheme)
|
||||
}
|
||||
|
||||
fun confirmDialog(context: Context, message: String, confirmButton: String, onConfirmed: (() -> Unit)) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(confirmButton) { _, _ -> onConfirmed() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun infoDialog(context: Context, messageId: Int) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(messageId)
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun infoDialog(context: Context, message: String) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a dialog that allows re-ordering and dis/enabling items (currently toolbar keys and popup keys).
|
||||
* The items are stored in a string pref in [key]. Each item contains a name and true/false, comma-separated.
|
||||
* Items are semicolon-separated, see e.g. [POPUP_KEYS_LABEL_DEFAULT] for an example.
|
||||
*/
|
||||
// this should probably be a class
|
||||
fun reorderDialog(
|
||||
context: Context,
|
||||
key: String,
|
||||
defaultSetting: String,
|
||||
@StringRes dialogTitleId: Int,
|
||||
getIcon: (String) -> Drawable? = { null }
|
||||
) {
|
||||
val prefs = context.prefs()
|
||||
val orderedItems = prefs.getString(key, defaultSetting)!!.split(";").mapTo(ArrayList()) {
|
||||
val both = it.split(",")
|
||||
both.first() to both.last().toBoolean()
|
||||
}
|
||||
val rv = RecyclerView(context)
|
||||
val bgColor = ContextCompat.getColor(context, R.color.sliding_items_background)
|
||||
val fgColor = ContextCompat.getColor(context, R.color.foreground)
|
||||
val padding = ResourceUtils.toPx(8, context.resources)
|
||||
rv.setPadding(3 * padding, padding, padding, padding)
|
||||
rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
|
||||
val callback = object : DiffUtil.ItemCallback<Pair<String, Boolean>>() {
|
||||
override fun areItemsTheSame(p0: Pair<String, Boolean>, p1: Pair<String, Boolean>) = p0 == p1
|
||||
override fun areContentsTheSame(p0: Pair<String, Boolean>, p1: Pair<String, Boolean>) = p0 == p1
|
||||
}
|
||||
|
||||
val adapter = object : ListAdapter<Pair<String, Boolean>, RecyclerView.ViewHolder>(callback) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val b = LayoutInflater.from(context).inflate(R.layout.reorder_dialog_item, rv, false)
|
||||
b.setBackgroundColor(bgColor)
|
||||
return object : RecyclerView.ViewHolder(b) { }
|
||||
}
|
||||
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||
val (text, wasChecked) = orderedItems[position]
|
||||
val displayText = text.lowercase().getStringResourceOrName("", context)
|
||||
viewHolder.itemView.findViewById<TextView>(R.id.reorder_item_name)?.text = displayText
|
||||
val switch = viewHolder.itemView.findViewById<Switch>(R.id.reorder_item_switch)
|
||||
switch?.setOnCheckedChangeListener(null)
|
||||
switch?.isChecked = wasChecked
|
||||
switch?.setOnCheckedChangeListener { _, isChecked ->
|
||||
val pos = orderedItems.indexOfFirst { it.first == text }
|
||||
orderedItems[pos] = text to isChecked
|
||||
}
|
||||
val icon = getIcon(text)
|
||||
viewHolder.itemView.findViewById<ImageView>(R.id.reorder_item_icon)?.let {
|
||||
it.visibility = if (icon == null) View.GONE else View.VISIBLE
|
||||
it.setColorFilter(fgColor, PorterDuff.Mode.SRC_IN)
|
||||
it.setImageDrawable(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
rv.adapter = adapter
|
||||
|
||||
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
|
||||
override fun onMove(rv: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
val pos1 = viewHolder.absoluteAdapterPosition
|
||||
val pos2 = target.absoluteAdapterPosition
|
||||
Collections.swap(orderedItems, pos1, pos2)
|
||||
adapter.notifyItemMoved(pos1, pos2)
|
||||
return true
|
||||
}
|
||||
override fun onSwiped(rv: RecyclerView.ViewHolder, direction: Int) { }
|
||||
}).attachToRecyclerView(rv)
|
||||
|
||||
adapter.submitList(orderedItems)
|
||||
|
||||
val builder = AlertDialog.Builder(context)
|
||||
.setTitle(dialogTitleId)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val value = orderedItems.joinToString(";") { it.first + "," + it.second }
|
||||
prefs.edit().putString(key, value).apply()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setView(rv)
|
||||
if (prefs.contains(key))
|
||||
builder.setNeutralButton(R.string.button_default) { _, _ ->
|
||||
prefs.edit().remove(key).apply()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -46,65 +42,6 @@ fun getDictionaryLocales(context: Context): MutableSet<Locale> {
|
|||
return locales
|
||||
}
|
||||
|
||||
fun showMissingDictionaryDialog(context: Context, locale: Locale) {
|
||||
val prefs = context.prefs()
|
||||
if (prefs.getBoolean(Settings.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG, Defaults.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG)
|
||||
|| locale.toString() == SubtypeLocaleUtils.NO_LANGUAGE)
|
||||
return
|
||||
val repositoryLink = "<a href='${Links.DICTIONARY_URL}'>" + context.getString(R.string.dictionary_link_text) + "</a>"
|
||||
val dictionaryLink = "<a href='${Links.DICTIONARY_URL}/src/branch/main/dictionaries/main_$locale.dict'>" + context.getString(
|
||||
R.string.dictionary_link_text) + "</a>"
|
||||
val startMessage = context.getString( // todo: now with the available dicts csv, is the full text still necessary?
|
||||
R.string.no_dictionary_message,
|
||||
repositoryLink,
|
||||
locale.toString(), // toString because that's how default AOSP dictionaries are named
|
||||
dictionaryLink,
|
||||
)
|
||||
val message = createDictionaryTextHtml(startMessage, locale, context)
|
||||
|
||||
val messageSpannable = SpannableStringUtils.fromHtml(message)
|
||||
val dialog = AlertDialog.Builder(context)
|
||||
.setTitle(R.string.no_dictionaries_available)
|
||||
.setMessage(messageSpannable)
|
||||
.setNegativeButton(R.string.dialog_close, null)
|
||||
.setNeutralButton(R.string.no_dictionary_dont_show_again_button) { _, _ ->
|
||||
prefs.edit { putBoolean(Settings.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG, true) }
|
||||
}
|
||||
.create()
|
||||
dialog.show()
|
||||
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
/** returns the [message], and if dictionaries for [locale] or language are available, a links to them */
|
||||
fun createDictionaryTextHtml(message: String, locale: Locale, context: Context): String {
|
||||
val knownDicts = mutableListOf<String>()
|
||||
context.assets.open("dictionaries_in_dict_repo.csv").reader().forEachLine {
|
||||
if (it.isBlank()) return@forEachLine
|
||||
val (type, localeString, experimental) = it.split(",")
|
||||
// we use a locale string here because that's in the dictionaries repo
|
||||
// ideally the repo would switch to language tag, but not sure how this is handled in the dictionary header
|
||||
// further, the dicts in the dictionaries repo should be compatible with other AOSP-based keyboards
|
||||
val dictLocale = localeString.constructLocale()
|
||||
if (LocaleUtils.getMatchLevel(locale, dictLocale) < LocaleUtils.LOCALE_GOOD_MATCH) return@forEachLine
|
||||
val rawDictString = "$type: ${dictLocale.getDisplayName(context.resources.configuration.locale())}"
|
||||
val dictString = if (experimental.isEmpty()) rawDictString
|
||||
else context.getString(R.string.available_dictionary_experimental, rawDictString)
|
||||
val dictBaseUrl = Links.DICTIONARY_URL + Links.DICTIONARY_DOWNLOAD_SUFFIX +
|
||||
if (experimental.isEmpty()) Links.DICTIONARY_NORMAL_SUFFIX else Links.DICTIONARY_EXPERIMENTAL_SUFFIX
|
||||
val dictLink = dictBaseUrl + type + "_" + localeString.lowercase() + ".dict"
|
||||
val fullText = "<li><a href='$dictLink'>$dictString</a></li>"
|
||||
knownDicts.add(fullText)
|
||||
}
|
||||
if (knownDicts.isEmpty()) return message
|
||||
return """
|
||||
<p>$message</p>
|
||||
<b>${context.getString(R.string.dictionary_available)}</b>
|
||||
<ul>
|
||||
${knownDicts.joinToString("\n")}
|
||||
</ul>
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
// why is this so horrible with annotated string?
|
||||
@Composable
|
||||
fun MissingDictionaryDialog(onDismissRequest: () -> Unit, locale: Locale) {
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.SharedPreferences
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
|
@ -15,8 +11,6 @@ import androidx.compose.ui.text.LinkAnnotation
|
|||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.fragment.app.commit
|
||||
import helium314.keyboard.latin.R
|
||||
|
||||
// generic extension functions
|
||||
|
||||
|
@ -80,15 +74,6 @@ fun Context.getActivity(): ComponentActivity? {
|
|||
return componentActivity
|
||||
}
|
||||
|
||||
// todo: should not be necessary after full pref switch to compose
|
||||
fun Activity.switchTo(fragment: androidx.fragment.app.Fragment) {
|
||||
(this as AppCompatActivity).supportFragmentManager.commit {
|
||||
findViewById<RelativeLayout>(R.id.settingsFragmentContainer).visibility = View.VISIBLE
|
||||
replace(R.id.settingsFragmentContainer, fragment)
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
|
||||
/** SharedPreferences from deviceProtectedContext, which are accessible even without unlocking.
|
||||
* They should not be used to store sensitive data! */
|
||||
fun Context.prefs(): SharedPreferences = DeviceProtectedUtils.getSharedPreferences(this)
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.InputType
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import helium314.keyboard.keyboard.Key
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
|
@ -16,8 +10,6 @@ import helium314.keyboard.keyboard.internal.KeyboardParams
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.LayoutParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.common.decodeBase36
|
||||
import helium314.keyboard.latin.common.encodeBase36
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
|
@ -25,63 +17,10 @@ import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
|||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import kotlinx.serialization.SerializationException
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.EnumMap
|
||||
import java.util.Locale
|
||||
|
||||
object LayoutUtilsCustom {
|
||||
fun loadLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) {
|
||||
if (uri == null)
|
||||
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
|
||||
val layoutContent: String
|
||||
try {
|
||||
val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile")
|
||||
FileUtils.copyContentUriToNewFile(uri, context, tmpFile)
|
||||
layoutContent = tmpFile.readText()
|
||||
tmpFile.delete()
|
||||
} catch (e: IOException) {
|
||||
return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file"))
|
||||
}
|
||||
|
||||
var name = ""
|
||||
context.contentResolver.query(uri, null, null, null, null).use {
|
||||
if (it != null && it.moveToFirst()) {
|
||||
val idx = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (idx >= 0)
|
||||
name = it.getString(idx).substringBeforeLast(".")
|
||||
}
|
||||
}
|
||||
loadLayout(layoutContent, name, languageTag, context, onAdded)
|
||||
}
|
||||
|
||||
fun loadLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) {
|
||||
var name = layoutName
|
||||
if (!checkLayout(layoutContent, context))
|
||||
return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
|
||||
// val isJson = checkLayout(layoutContent, context)
|
||||
// ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.title_layout_name_select)
|
||||
.setView(EditText(context).apply {
|
||||
setText(name)
|
||||
doAfterTextChanged { name = it.toString() }
|
||||
val padding = ResourceUtils.toPx(8, context.resources)
|
||||
setPadding(3 * padding, padding, 3 * padding, padding)
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
// name must be encoded to avoid issues with validity of subtype extra string or file name
|
||||
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}."
|
||||
val file = getLayoutFile(name, LayoutType.MAIN, context)
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
file.parentFile?.mkdir()
|
||||
file.writeText(layoutContent)
|
||||
onAdded(name)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun checkLayout(layoutContent: String, context: Context): Boolean {
|
||||
if (Settings.getValues() == null)
|
||||
|
@ -204,42 +143,6 @@ object LayoutUtilsCustom {
|
|||
return file
|
||||
}
|
||||
|
||||
fun editLayout(layoutName: String, context: Context, startContent: String? = null, displayName: CharSequence? = null) {
|
||||
val file = getLayoutFile(layoutName, LayoutType.MAIN, context)
|
||||
val editText = EditText(context).apply {
|
||||
setText(startContent ?: file.readText())
|
||||
}
|
||||
val builder = AlertDialog.Builder(context)
|
||||
.setTitle(getDisplayName(layoutName))
|
||||
.setView(editText)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
val content = editText.text.toString()
|
||||
if (!checkLayout(content, context)) {
|
||||
editLayout(layoutName, context, content)
|
||||
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
|
||||
} else {
|
||||
file.parentFile?.mkdir()
|
||||
file.writeText(content)
|
||||
onLayoutFileChanged()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (displayName != null) {
|
||||
if (file.exists()) {
|
||||
builder.setNeutralButton(R.string.delete) { _, _ ->
|
||||
confirmDialog(context, context.getString(R.string.delete_layout, displayName), context.getString(R.string.delete)) {
|
||||
file.delete()
|
||||
onLayoutFileChanged()
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setTitle(displayName)
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
// this goes into prefs and file names, so do not change!
|
||||
const val CUSTOM_LAYOUT_PREFIX = "custom."
|
||||
private const val TAG = "LayoutUtilsCustom"
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import helium314.keyboard.compat.locale
|
||||
import helium314.keyboard.dictionarypack.DictionaryPackConstants
|
||||
import helium314.keyboard.latin.Dictionary
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.ReadOnlyBinaryDictionary
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.common.LocaleUtils
|
||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||
import helium314.keyboard.latin.makedict.DictionaryHeader
|
||||
import helium314.keyboard.latin.settings.*
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class NewDictionaryAdder(private val context: Context, private val onAdded: ((Boolean, File) -> Unit)?) {
|
||||
private val cachedDictionaryFile = File(context.cacheDir.path + File.separator + "temp_dict")
|
||||
|
||||
fun addDictionary(uri: Uri?, mainLocale: Locale?) {
|
||||
if (uri == null)
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
|
||||
cachedDictionaryFile.delete()
|
||||
try {
|
||||
FileUtils.copyContentUriToNewFile(uri, context, cachedDictionaryFile)
|
||||
} catch (e: IOException) {
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
|
||||
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
|
||||
?: return onDictionaryLoadingError(R.string.dictionary_file_error)
|
||||
val locale = newHeader.mLocaleString.constructLocale()
|
||||
|
||||
val dict = ReadOnlyBinaryDictionary(cachedDictionaryFile.absolutePath, 0, cachedDictionaryFile.length(), false, locale, "test")
|
||||
if (!dict.isValidDictionary) {
|
||||
dict.close()
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
|
||||
if (mainLocale == null) {
|
||||
val localeName = LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context)
|
||||
val message = context.getString(R.string.add_new_dictionary_ask_locale,
|
||||
newHeader.mIdString.substringBefore(":"),
|
||||
localeName
|
||||
)
|
||||
val b = AlertDialog.Builder(context)
|
||||
.setTitle(R.string.add_new_dictionary_title)
|
||||
.setMessage(message)
|
||||
.setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
|
||||
.setNegativeButton(R.string.button_select_language) { _, _ -> selectLocaleForDictionary(newHeader, locale) }
|
||||
if (SubtypeSettings.hasMatchingSubtypeForLocale(locale)) {
|
||||
val buttonText = context.getString(R.string.button_add_to_language, localeName)
|
||||
b.setPositiveButton(buttonText) { _, _ ->
|
||||
addDictAndAskToReplace(newHeader, locale)
|
||||
}
|
||||
}
|
||||
b.show()
|
||||
return
|
||||
}
|
||||
|
||||
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not,
|
||||
// e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible
|
||||
if (locale.script() != mainLocale.script())
|
||||
return onDictionaryLoadingError(R.string.dictionary_file_wrong_script)
|
||||
|
||||
if (locale != mainLocale) {
|
||||
val message = context.resources.getString(
|
||||
R.string.dictionary_file_wrong_locale,
|
||||
LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context),
|
||||
LocaleUtils.getLocaleDisplayNameInSystemLocale(mainLocale, context)
|
||||
)
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
|
||||
.setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ ->
|
||||
addDictAndAskToReplace(newHeader, mainLocale)
|
||||
}
|
||||
.show()
|
||||
return
|
||||
}
|
||||
addDictAndAskToReplace(newHeader, mainLocale)
|
||||
}
|
||||
|
||||
private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) {
|
||||
val locales = SubtypeSettings.getAvailableSubtypeLocales().sortedBy { it.language != dictLocale.language } // matching languages should show first
|
||||
val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray()
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.button_select_language)
|
||||
.setItems(displayNamesArray) { di, i ->
|
||||
di.dismiss()
|
||||
locales.forEachIndexed { index, locale ->
|
||||
if (index == i)
|
||||
addDictAndAskToReplace(newHeader, locale)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun addDictAndAskToReplace(header: DictionaryHeader, mainLocale: Locale) {
|
||||
val dictionaryType = header.mIdString.substringBefore(":")
|
||||
val cacheDir = DictionaryInfoUtils.getAndCreateCacheDirectoryForLocale(mainLocale, context)
|
||||
val dictFile = File(cacheDir, dictionaryType + "_" + DictionaryInfoUtils.USER_DICTIONARY_SUFFIX)
|
||||
|
||||
fun moveDict(replaced: Boolean) {
|
||||
if (!cachedDictionaryFile.renameTo(dictFile)) {
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
if (dictionaryType == Dictionary.TYPE_MAIN) {
|
||||
// replaced main dict, remove the one created from internal data
|
||||
val internalMainDictFile = File(cacheDir, DictionaryInfoUtils.getExtractedMainDictFilename())
|
||||
internalMainDictFile.delete()
|
||||
}
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
context.sendBroadcast(newDictBroadcast)
|
||||
onAdded?.let { it(replaced, dictFile) }
|
||||
}
|
||||
|
||||
if (!dictFile.exists()) {
|
||||
return moveDict(false)
|
||||
}
|
||||
|
||||
val systemLocale = context.resources.configuration.locale()
|
||||
val newInfo = header.info(systemLocale)
|
||||
val oldInfo = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())?.info(systemLocale)
|
||||
confirmDialog(context,
|
||||
context.getString(R.string.replace_dictionary_message, dictionaryType, oldInfo, newInfo),
|
||||
context.getString(R.string.replace_dictionary)
|
||||
) { moveDict(true) }
|
||||
}
|
||||
|
||||
private fun onDictionaryLoadingError(messageId: Int) {
|
||||
cachedDictionaryFile.delete()
|
||||
infoDialog(context, messageId)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import helium314.keyboard.keyboard.Key
|
||||
import helium314.keyboard.keyboard.internal.KeySpecParser
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
|
@ -97,7 +96,7 @@ private fun transformLabel(label: String, params: KeyboardParams): String =
|
|||
label.rtlLabel(params)
|
||||
} else label
|
||||
|
||||
/** returns a list of enabled popup keys for pref [key] */
|
||||
/** returns a list of enabled popup keys */
|
||||
fun getEnabledPopupKeys(string: String): List<String> {
|
||||
return string.split(Separators.ENTRY).mapNotNull {
|
||||
val split = it.split(Separators.KV)
|
||||
|
|
|
@ -15,14 +15,10 @@ import android.graphics.Rect;
|
|||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
|
||||
import androidx.core.util.TypedValueCompat;
|
||||
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.settings.SettingsValues;
|
||||
|
||||
|
@ -137,8 +133,4 @@ public final class ResourceUtils {
|
|||
public static boolean isNight(final Resources res) {
|
||||
return (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
public static int toPx(final int dp, final Resources res) {
|
||||
return (int) TypedValueCompat.dpToPx(dp, res.getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,8 +123,6 @@ object SubtypeSettings {
|
|||
|
||||
fun getSystemLocales(): List<Locale> = systemLocales.toList()
|
||||
|
||||
fun hasMatchingSubtypeForLocale(locale: Locale): Boolean = !resourceSubtypesByLocale[locale].isNullOrEmpty()
|
||||
|
||||
fun getResourceSubtypesForLocale(locale: Locale): List<InputMethodSubtype> = resourceSubtypesByLocale[locale].orEmpty()
|
||||
|
||||
fun getAvailableSubtypeLocales(): List<Locale> = resourceSubtypesByLocale.keys.toList()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package helium314.keyboard.latin.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder
|
||||
|
@ -50,16 +49,6 @@ object SubtypeUtilsAdditional {
|
|||
fun createEmojiCapableAdditionalSubtype(locale: Locale, mainLayoutName: String, asciiCapable: Boolean) =
|
||||
createAdditionalSubtype(locale, "${ExtraValue.KEYBOARD_LAYOUT_SET}=MAIN${Separators.KV}$mainLayoutName", asciiCapable, true)
|
||||
|
||||
// todo: consider using SettingsSubtype (nah, this can be removed after removing old settings)
|
||||
fun addAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
||||
val additionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString).toMutableSet()
|
||||
additionalSubtypes.add(subtype)
|
||||
val newAdditionalSubtypesString = createPrefSubtypes(additionalSubtypes)
|
||||
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
||||
}
|
||||
|
||||
// todo: SettingsSubtype?
|
||||
fun removeAdditionalSubtype(context: Context, subtype: InputMethodSubtype) {
|
||||
val prefs = context.prefs()
|
||||
SubtypeSettings.removeEnabledSubtype(context, subtype)
|
||||
|
|
|
@ -7,11 +7,11 @@ import android.content.SharedPreferences
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowInsets.Type
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -22,16 +22,15 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isGone
|
||||
import helium314.keyboard.compat.locale
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.InputAttributes
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
import helium314.keyboard.latin.define.DebugFlags
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
import helium314.keyboard.latin.utils.ExecutorUtils
|
||||
import helium314.keyboard.latin.utils.ResourceUtils
|
||||
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils
|
||||
import helium314.keyboard.latin.utils.cleanUnusedMainDicts
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
|
@ -51,7 +50,7 @@ import java.util.zip.ZipOutputStream
|
|||
// https://developer.android.com/codelabs/jetpack-compose-performance#2
|
||||
// https://developer.android.com/topic/performance/baselineprofiles/overview
|
||||
// todo: consider viewModel, at least for LanguageScreen and ColorsScreen it might help making them less awkward and complicated
|
||||
class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
class SettingsActivity : ComponentActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private val prefs by lazy { this.prefs() }
|
||||
val prefChanged = MutableStateFlow(0) // simple counter, as the only relevant information is that something changed
|
||||
private val dictUriFlow = MutableStateFlow<Uri?>(null)
|
||||
|
@ -68,6 +67,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
|
|||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute { cleanUnusedMainDicts(this) }
|
||||
if (BuildConfig.DEBUG || DebugFlags.DEBUG_ENABLED)
|
||||
crashReportFiles.value = findCrashReports()
|
||||
setStatusBarIconColor()
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
||||
// with this the layout edit dialog is not covered by the keyboard
|
||||
|
@ -84,15 +84,9 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
|
|||
|
||||
val spellchecker = intent?.getBooleanExtra("spellchecker", false) ?: false
|
||||
|
||||
// todo: when removing old settings completely, remove settings_activity.xml and supportFragmentManager stuff
|
||||
// val cv = ComposeView(context = this)
|
||||
// setContentView(cv)
|
||||
setContentView(R.layout.settings_activity)
|
||||
supportFragmentManager.addOnBackStackChangedListener {
|
||||
updateContainerVisibility()
|
||||
}
|
||||
// cv.setContent { // todo: when removing old settings
|
||||
findViewById<ComposeView>(R.id.navHost).setContent {
|
||||
val cv = ComposeView(context = this)
|
||||
setContentView(cv)
|
||||
cv.setContent {
|
||||
Theme {
|
||||
Surface {
|
||||
val dictUri by dictUriFlow.collectAsState()
|
||||
|
@ -108,14 +102,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
|
|||
settingsContainer[Settings.PREF_BLOCK_POTENTIALLY_OFFENSIVE]!!.Preference()
|
||||
}
|
||||
else
|
||||
SettingsNavHost(
|
||||
onClickBack = {
|
||||
// this.finish() // todo: when removing old settings
|
||||
if (supportFragmentManager.findFragmentById(R.id.settingsFragmentContainer) == null)
|
||||
this.finish()
|
||||
else supportFragmentManager.popBackStack()
|
||||
}
|
||||
)
|
||||
SettingsNavHost(onClickBack = { this.finish() })
|
||||
if (dictUri != null) {
|
||||
NewDictionaryDialog(
|
||||
onDismissRequest = { dictUriFlow.value = null },
|
||||
|
@ -157,10 +144,6 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateContainerVisibility() { // todo: remove when removing old settings
|
||||
findViewById<RelativeLayout>(R.id.settingsFragmentContainer).isGone = supportFragmentManager.findFragmentById(R.id.settingsFragmentContainer) == null
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||
|
@ -225,6 +208,16 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
|
|||
}
|
||||
}
|
||||
|
||||
// deprecated but works... ideally it would be done automatically like it worked before switching to compose
|
||||
private fun setStatusBarIconColor() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
|
||||
val view = window.decorView
|
||||
if (ResourceUtils.isNight(resources))
|
||||
view.systemUiVisibility = view.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
else
|
||||
view.systemUiVisibility = view.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
|
||||
companion object {
|
||||
// public write so compose previews can show the screens
|
||||
// having it in a companion object is not ideal as it will stay in memory even after settings are closed
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
|
@ -13,14 +13,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import helium314.keyboard.dictionarypack.DictionaryPackConstants
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMBER
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMPAD
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_NUMPAD_LANDSCAPE
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_PHONE
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_PHONE_SYMBOLS
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_ARABIC
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_SHIFTED
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.checkVersionUpgrade
|
||||
import helium314.keyboard.latin.common.FileUtils
|
||||
|
@ -277,7 +269,7 @@ private fun upgradeFileNames(originalName: String): String {
|
|||
originalName.startsWith("layouts") -> {
|
||||
// replace file name after switch to language tag, but only if it's not a layout
|
||||
val localeString = originalName.substringAfter(".").substringBefore(".")
|
||||
if (localeString in listOf(LAYOUT_SYMBOLS, LAYOUT_SYMBOLS_SHIFTED, LAYOUT_SYMBOLS_ARABIC, LAYOUT_NUMBER, LAYOUT_NUMPAD, LAYOUT_NUMPAD_LANDSCAPE, LAYOUT_PHONE, LAYOUT_PHONE_SYMBOLS))
|
||||
if (localeString in listOf("symbols", "symbols_shifted", "symbols_arabic", "number", "numpad", "numpad_landscape", "phone", "phone_symbols"))
|
||||
return originalName // it's a layout!
|
||||
val locale = localeString.constructLocale()
|
||||
if (locale.toLanguageTag() != "und")
|
||||
|
|
|
@ -3,7 +3,6 @@ package helium314.keyboard.settings.preferences
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -59,11 +58,6 @@ fun LoadGestureLibPreference(setting: Setting) {
|
|||
renameToLibFileAndRestart(tmpfile, checksum)
|
||||
} else {
|
||||
tempFilePath = tmpfile.absolutePath
|
||||
AlertDialog.Builder(ctx)
|
||||
.setMessage(ctx.getString(R.string.checksum_mismatch_message, abi))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> renameToLibFileAndRestart(tmpfile, checksum) }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> tmpfile.delete() }
|
||||
.show()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
tmpfile.delete()
|
||||
|
|
|
@ -118,7 +118,7 @@ private fun PreferencePreview() {
|
|||
Preference(
|
||||
name = "Preference with icon",
|
||||
onClick = {},
|
||||
icon = R.drawable.ic_settings_about_foreground
|
||||
icon = R.drawable.ic_settings_about
|
||||
)
|
||||
SliderPreference(
|
||||
name = "SliderPreference",
|
||||
|
@ -131,7 +131,7 @@ private fun PreferencePreview() {
|
|||
name = "Preference with icon and description",
|
||||
description = "some text",
|
||||
onClick = {},
|
||||
icon = R.drawable.ic_settings_about_foreground
|
||||
icon = R.drawable.ic_settings_about
|
||||
)
|
||||
Preference(
|
||||
name = "Preference with switch",
|
||||
|
|
|
@ -87,7 +87,7 @@ fun createAboutSettings(context: Context) = listOf(
|
|||
prefs.edit().putBoolean(DebugSettings.PREF_SHOW_DEBUG_SETTINGS, true).apply()
|
||||
Toast.makeText(ctx, R.string.prefs_debug_settings_enabled, Toast.LENGTH_LONG).show()
|
||||
},
|
||||
icon = R.drawable.ic_settings_about_foreground
|
||||
icon = R.drawable.ic_settings_about
|
||||
)
|
||||
},
|
||||
Setting(context, SettingsWithoutKey.LICENSE, R.string.license, R.string.gnu_gpl) {
|
||||
|
@ -101,7 +101,7 @@ fun createAboutSettings(context: Context) = listOf(
|
|||
intent.action = Intent.ACTION_VIEW
|
||||
ctx.startActivity(intent)
|
||||
},
|
||||
icon = R.drawable.ic_settings_about_license_foreground
|
||||
icon = R.drawable.ic_settings_about_license
|
||||
)
|
||||
},
|
||||
Setting(context, SettingsWithoutKey.HIDDEN_FEATURES, R.string.hidden_features_title, R.string.hidden_features_summary) {
|
||||
|
@ -125,7 +125,7 @@ fun createAboutSettings(context: Context) = listOf(
|
|||
builder.show()
|
||||
(builder.findViewById<View>(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance()
|
||||
},
|
||||
icon = R.drawable.ic_settings_about_hidden_features_foreground
|
||||
icon = R.drawable.ic_settings_about_hidden_features
|
||||
)
|
||||
},
|
||||
Setting(context, SettingsWithoutKey.GITHUB, R.string.about_github_link) {
|
||||
|
@ -139,7 +139,7 @@ fun createAboutSettings(context: Context) = listOf(
|
|||
intent.action = Intent.ACTION_VIEW
|
||||
ctx.startActivity(intent)
|
||||
},
|
||||
icon = R.drawable.ic_settings_about_github_foreground
|
||||
icon = R.drawable.ic_settings_about_github
|
||||
)
|
||||
},
|
||||
Setting(context, SettingsWithoutKey.SAVE_LOG, R.string.save_log) { setting ->
|
||||
|
@ -169,7 +169,7 @@ fun createAboutSettings(context: Context) = listOf(
|
|||
.setType("text/plain")
|
||||
launcher.launch(intent)
|
||||
},
|
||||
icon = R.drawable.ic_settings_about_log_foreground
|
||||
icon = R.drawable.ic_settings_about_log
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -108,6 +108,7 @@ fun createAppearanceSettings(context: Context) = listOf(
|
|||
if (prefs.getString(Settings.PREF_THEME_COLORS_NIGHT, Defaults.PREF_THEME_COLORS_NIGHT) == KeyboardTheme.THEME_HOLO_WHITE)
|
||||
prefs.edit().remove(Settings.PREF_THEME_COLORS_NIGHT).apply()
|
||||
}
|
||||
KeyboardIconsSet.needsReload = true // only relevant for Settings.PREF_CUSTOM_ICON_NAMES
|
||||
}
|
||||
},
|
||||
Setting(context, Settings.PREF_ICON_STYLE, R.string.icon_style) { setting ->
|
||||
|
@ -117,7 +118,10 @@ fun createAppearanceSettings(context: Context) = listOf(
|
|||
setting,
|
||||
items,
|
||||
Defaults.PREF_ICON_STYLE
|
||||
) { KeyboardSwitcher.getInstance().setThemeNeedsReload() }
|
||||
) {
|
||||
KeyboardIconsSet.needsReload = true // only relevant for Settings.PREF_CUSTOM_ICON_NAMES
|
||||
KeyboardSwitcher.getInstance().setThemeNeedsReload()
|
||||
}
|
||||
},
|
||||
Setting(context, Settings.PREF_CUSTOM_ICON_NAMES, R.string.customize_icons) { setting ->
|
||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
@ -126,11 +130,8 @@ fun createAppearanceSettings(context: Context) = listOf(
|
|||
onClick = { showDialog = true }
|
||||
)
|
||||
if (showDialog) {
|
||||
/* if (keyboardNeedsReload) {
|
||||
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(LocalContext.current)
|
||||
keyboardNeedsReload = false
|
||||
}
|
||||
*/ CustomizeIconsDialog(setting.key) { showDialog = false }
|
||||
KeyboardIconsSet.instance.loadIcons(LocalContext.current)
|
||||
CustomizeIconsDialog(setting.key) { showDialog = false }
|
||||
}
|
||||
},
|
||||
Setting(context, Settings.PREF_THEME_COLORS, R.string.theme_colors) { setting ->
|
||||
|
|
|
@ -16,7 +16,6 @@ import helium314.keyboard.latin.DictionaryDumpBroadcastReceiver
|
|||
import helium314.keyboard.latin.DictionaryFacilitator
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.settings.DebugSettings
|
||||
import helium314.keyboard.latin.settings.DebugSettingsFragment
|
||||
import helium314.keyboard.latin.settings.Defaults
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
import helium314.keyboard.settings.Setting
|
||||
|
@ -41,7 +40,7 @@ fun DebugScreen(
|
|||
DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH,
|
||||
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW,
|
||||
R.string.prefs_dump_dynamic_dicts
|
||||
) + DictionaryFacilitator.DYNAMIC_DICTIONARY_TYPES.map { DebugSettingsFragment.PREF_KEY_DUMP_DICT_PREFIX + it }
|
||||
) + DictionaryFacilitator.DYNAMIC_DICTIONARY_TYPES.map { DebugSettings.PREF_KEY_DUMP_DICT_PREFIX + it }
|
||||
SearchSettingsScreen(
|
||||
onClickBack = {
|
||||
if (needsRestart) {
|
||||
|
@ -95,7 +94,7 @@ private fun createDebugSettings(context: Context) = listOf(
|
|||
SwitchPreference(def, Defaults.PREF_SLIDING_KEY_INPUT_PREVIEW)
|
||||
},
|
||||
) + DictionaryFacilitator.DYNAMIC_DICTIONARY_TYPES.map { type ->
|
||||
Setting(context, DebugSettingsFragment.PREF_KEY_DUMP_DICT_PREFIX + type, R.string.button_default) {
|
||||
Setting(context, DebugSettings.PREF_KEY_DUMP_DICT_PREFIX + type, R.string.button_default) {
|
||||
val ctx = LocalContext.current
|
||||
Preference(
|
||||
name = "Dump $type dictionary",
|
||||
|
|
|
@ -11,22 +11,11 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.settings.AboutFragment
|
||||
import helium314.keyboard.latin.settings.AdvancedSettingsFragment
|
||||
import helium314.keyboard.latin.settings.AppearanceSettingsFragment
|
||||
import helium314.keyboard.latin.settings.CorrectionSettingsFragment
|
||||
import helium314.keyboard.latin.settings.GestureSettingsFragment
|
||||
import helium314.keyboard.latin.settings.LanguageSettingsFragment
|
||||
import helium314.keyboard.latin.settings.PreferencesSettingsFragment
|
||||
import helium314.keyboard.latin.settings.ToolbarSettingsFragment
|
||||
import helium314.keyboard.latin.utils.JniUtils
|
||||
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||
import helium314.keyboard.latin.utils.displayName
|
||||
import helium314.keyboard.latin.utils.getActivity
|
||||
import helium314.keyboard.latin.utils.switchTo
|
||||
import helium314.keyboard.settings.NextScreenIcon
|
||||
import helium314.keyboard.settings.preferences.Preference
|
||||
import helium314.keyboard.settings.preferences.PreferenceCategory
|
||||
import helium314.keyboard.settings.SearchSettingsScreen
|
||||
import helium314.keyboard.settings.Theme
|
||||
import helium314.keyboard.settings.initPreview
|
||||
|
@ -58,33 +47,33 @@ fun MainSettingsScreen(
|
|||
name = stringResource(R.string.language_and_layouts_title),
|
||||
description = enabledSubtypes.joinToString(", ") { it.displayName(ctx) },
|
||||
onClick = onClickLanguage,
|
||||
icon = R.drawable.ic_settings_languages_foreground
|
||||
icon = R.drawable.ic_settings_languages
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_preferences),
|
||||
onClick = onClickPreferences,
|
||||
icon = R.drawable.ic_settings_preferences_foreground
|
||||
icon = R.drawable.ic_settings_preferences
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_appearance),
|
||||
onClick = onClickAppearance,
|
||||
icon = R.drawable.ic_settings_appearance_foreground
|
||||
icon = R.drawable.ic_settings_appearance
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_toolbar),
|
||||
onClick = onClickToolbar,
|
||||
icon = R.drawable.ic_settings_toolbar_foreground
|
||||
icon = R.drawable.ic_settings_toolbar
|
||||
) { NextScreenIcon() }
|
||||
if (JniUtils.sHaveGestureLib)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_gesture),
|
||||
onClick = onClickGestureTyping,
|
||||
icon = R.drawable.ic_settings_gesture_foreground
|
||||
icon = R.drawable.ic_settings_gesture
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_correction),
|
||||
onClick = onClickTextCorrection,
|
||||
icon = R.drawable.ic_settings_correction_foreground
|
||||
icon = R.drawable.ic_settings_correction
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_secondary_layouts),
|
||||
|
@ -99,47 +88,13 @@ fun MainSettingsScreen(
|
|||
Preference(
|
||||
name = stringResource(R.string.settings_screen_advanced),
|
||||
onClick = onClickAdvanced,
|
||||
icon = R.drawable.ic_settings_advanced_foreground
|
||||
icon = R.drawable.ic_settings_advanced
|
||||
) { NextScreenIcon() }
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_about),
|
||||
onClick = onClickAbout,
|
||||
icon = R.drawable.ic_settings_about_foreground
|
||||
icon = R.drawable.ic_settings_about
|
||||
) { NextScreenIcon() }
|
||||
PreferenceCategory(title = "old screens")
|
||||
Preference(
|
||||
name = stringResource(R.string.language_and_layouts_title),
|
||||
onClick = { ctx.getActivity()?.switchTo(LanguageSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_preferences),
|
||||
onClick = { ctx.getActivity()?.switchTo(PreferencesSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_appearance),
|
||||
onClick = { ctx.getActivity()?.switchTo(AppearanceSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_toolbar),
|
||||
onClick = { ctx.getActivity()?.switchTo(ToolbarSettingsFragment()) }
|
||||
)
|
||||
if (JniUtils.sHaveGestureLib)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_gesture),
|
||||
onClick = { ctx.getActivity()?.switchTo(GestureSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_correction),
|
||||
onClick = { ctx.getActivity()?.switchTo(CorrectionSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_advanced),
|
||||
onClick = { ctx.getActivity()?.switchTo(AdvancedSettingsFragment()) }
|
||||
)
|
||||
Preference(
|
||||
name = stringResource(R.string.settings_screen_about),
|
||||
onClick = { ctx.getActivity()?.switchTo(AboutFragment()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ fun TextCorrectionScreen(
|
|||
Settings.PREF_AUTO_CORRECTION,
|
||||
if (autocorrectEnabled) Settings.PREF_MORE_AUTO_CORRECTION else null,
|
||||
if (autocorrectEnabled) Settings.PREF_AUTOCORRECT_SHORTCUTS else null,
|
||||
if (autocorrectEnabled) Settings.PREF_AUTO_CORRECTION_CONFIDENCE else null,
|
||||
if (autocorrectEnabled) Settings.PREF_AUTO_CORRECT_THRESHOLD else null,
|
||||
Settings.PREF_AUTO_CAP,
|
||||
Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD,
|
||||
Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION,
|
||||
|
@ -105,13 +105,14 @@ fun createCorrectionSettings(context: Context) = listOf(
|
|||
) {
|
||||
SwitchPreference(it, Defaults.PREF_AUTOCORRECT_SHORTCUTS)
|
||||
},
|
||||
Setting(context, Settings.PREF_AUTO_CORRECTION_CONFIDENCE, R.string.auto_correction_confidence) {
|
||||
Setting(context, Settings.PREF_AUTO_CORRECT_THRESHOLD, R.string.auto_correction_confidence) {
|
||||
val items = listOf(
|
||||
stringResource(R.string.auto_correction_threshold_mode_modest) to "0",
|
||||
stringResource(R.string.auto_correction_threshold_mode_aggressive) to "1",
|
||||
stringResource(R.string.auto_correction_threshold_mode_very_aggressive) to "2",
|
||||
stringResource(R.string.auto_correction_threshold_mode_modest) to 0.185f,
|
||||
stringResource(R.string.auto_correction_threshold_mode_aggressive) to 0.067f,
|
||||
stringResource(R.string.auto_correction_threshold_mode_very_aggressive) to -1f,
|
||||
)
|
||||
ListPreference(it, items, Defaults.PREF_AUTO_CORRECTION_CONFIDENCE)
|
||||
// todo: consider making it a slider, and maybe somehow adjust range so we can show %
|
||||
ListPreference(it, items, Defaults.PREF_AUTO_CORRECT_THRESHOLD)
|
||||
},
|
||||
Setting(context, Settings.PREF_AUTO_CAP,
|
||||
R.string.auto_cap, R.string.auto_cap_summary
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue