mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-06-01 04:12:12 +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
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId = "helium314.keyboard"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 2309
|
||||
versionName = "2.3+dev8"
|
||||
versionCode = 2310
|
||||
versionName = "2.3+dev9"
|
||||
ndk {
|
||||
abiFilters.clear()
|
||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||
|
@ -100,7 +100,6 @@ dependencies {
|
|||
// androidx
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.autofill:autofill:1.1.0")
|
||||
|
||||
|
@ -117,9 +116,6 @@ dependencies {
|
|||
implementation("sh.calvin.reorderable:reorderable:2.4.2") // for easier re-ordering
|
||||
implementation("com.github.skydoves:colorpicker-compose:1.1.2") // for user-defined colors
|
||||
|
||||
// color picker for user-defined colors
|
||||
implementation("com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0")
|
||||
|
||||
// test
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
|
|
|
@ -49,41 +49,15 @@ SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
|||
</service>
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".setup.SetupActivity"
|
||||
android:theme="@style/platformActivityTheme"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".permissions.PermissionsActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="false"
|
||||
android:taskAffinity="" >
|
||||
</activity>
|
||||
|
||||
<activity android:name=".setup.SetupWizardActivity"
|
||||
android:theme="@style/platformActivityTheme"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="helium314.keyboard.settings.SettingsActivity"
|
||||
android:theme="@style/platformActivityTheme"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:label="@string/ime_settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- intent filter for opening .dict files -->
|
||||
<intent-filter>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 The Android Open Source Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_focused="true"
|
||||
android:color="@color/setup_step_action_pressed" />
|
||||
<item
|
||||
android:state_pressed="true"
|
||||
android:color="@color/setup_step_action_pressed" />
|
||||
<item
|
||||
android:color="@color/setup_step_background" />
|
||||
</selector>
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_focused="true"
|
||||
android:color="@color/setup_step_action_text_pressed" />
|
||||
<item
|
||||
android:state_pressed="true"
|
||||
android:color="@color/setup_step_action_text_pressed" />
|
||||
<item
|
||||
android:color="@color/setup_text_action" />
|
||||
</selector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_about_foreground"/>
|
||||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_about_github_foreground"/>
|
||||
<!--
|
||||
icon from icons8.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M10.9,2.1c-4.6,0.5 -8.3,4.2 -8.8,8.7c-0.5,4.7 2.2,8.9 6.3,10.5C8.7,21.4 9,21.2 9,20.8v-1.6c0,0 -0.4,0.1 -0.9,0.1c-1.4,0 -2,-1.2 -2.1,-1.9c-0.1,-0.4 -0.3,-0.7 -0.6,-1C5.1,16.3 5,16.3 5,16.2C5,16 5.3,16 5.4,16c0.6,0 1.1,0.7 1.3,1c0.5,0.8 1.1,1 1.4,1c0.4,0 0.7,-0.1 0.9,-0.2c0.1,-0.7 0.4,-1.4 1,-1.8c-2.3,-0.5 -4,-1.8 -4,-4c0,-1.1 0.5,-2.2 1.2,-3C7.1,8.8 7,8.3 7,7.6C7,7.2 7,6.6 7.3,6c0,0 1.4,0 2.8,1.3C10.6,7.1 11.3,7 12,7s1.4,0.1 2,0.3C15.3,6 16.8,6 16.8,6C17,6.6 17,7.2 17,7.6c0,0.8 -0.1,1.2 -0.2,1.4c0.7,0.8 1.2,1.8 1.2,3c0,2.2 -1.7,3.5 -4,4c0.6,0.5 1,1.4 1,2.3v2.6c0,0.3 0.3,0.6 0.7,0.5c3.7,-1.5 6.3,-5.1 6.3,-9.3C22,6.1 16.9,1.4 10.9,2.1z"/>
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon from icons8.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M10.9,2.1c-4.6,0.5 -8.3,4.2 -8.8,8.7c-0.5,4.7 2.2,8.9 6.3,10.5C8.7,21.4 9,21.2 9,20.8v-1.6c0,0 -0.4,0.1 -0.9,0.1c-1.4,0 -2,-1.2 -2.1,-1.9c-0.1,-0.4 -0.3,-0.7 -0.6,-1C5.1,16.3 5,16.3 5,16.2C5,16 5.3,16 5.4,16c0.6,0 1.1,0.7 1.3,1c0.5,0.8 1.1,1 1.4,1c0.4,0 0.7,-0.1 0.9,-0.2c0.1,-0.7 0.4,-1.4 1,-1.8c-2.3,-0.5 -4,-1.8 -4,-4c0,-1.1 0.5,-2.2 1.2,-3C7.1,8.8 7,8.3 7,7.6C7,7.2 7,6.6 7.3,6c0,0 1.4,0 2.8,1.3C10.6,7.1 11.3,7 12,7s1.4,0.1 2,0.3C15.3,6 16.8,6 16.8,6C17,6.6 17,7.2 17,7.6c0,0.8 -0.1,1.2 -0.2,1.4c0.7,0.8 1.2,1.8 1.2,3c0,2.2 -1.7,3.5 -4,4c0.6,0.5 1,1.4 1,2.3v2.6c0,0.3 0.3,0.6 0.7,0.5c3.7,-1.5 6.3,-5.1 6.3,-9.3C22,6.1 16.9,1.4 10.9,2.1z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_about_hidden_features_foreground"/>
|
||||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_about_license_foreground"/>
|
||||
<!--
|
||||
icon from pictogrammers.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M9 10A3.04 3.04 0 0 1 12 7A3.04 3.04 0 0 1 15 10A3.04 3.04 0 0 1 12 13A3.04 3.04 0 0 1 9 10M12 19L16 20V16.92A7.54 7.54 0 0 1 12 18A7.54 7.54 0 0 1 8 16.92V20M12 4A5.78 5.78 0 0 0 7.76 5.74A5.78 5.78 0 0 0 6 10A5.78 5.78 0 0 0 7.76 14.23A5.78 5.78 0 0 0 12 16A5.78 5.78 0 0 0 16.24 14.23A5.78 5.78 0 0 0 18 10A5.78 5.78 0 0 0 16.24 5.74A5.78 5.78 0 0 0 12 4M20 10A8.04 8.04 0 0 1 19.43 12.8A7.84 7.84 0 0 1 18 15.28V23L12 21L6 23V15.28A7.9 7.9 0 0 1 4 10A7.68 7.68 0 0 1 6.33 4.36A7.73 7.73 0 0 1 12 2A7.73 7.73 0 0 1 17.67 4.36A7.68 7.68 0 0 1 20 10Z"/>
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon from pictogrammers.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M9 10A3.04 3.04 0 0 1 12 7A3.04 3.04 0 0 1 15 10A3.04 3.04 0 0 1 12 13A3.04 3.04 0 0 1 9 10M12 19L16 20V16.92A7.54 7.54 0 0 1 12 18A7.54 7.54 0 0 1 8 16.92V20M12 4A5.78 5.78 0 0 0 7.76 5.74A5.78 5.78 0 0 0 6 10A5.78 5.78 0 0 0 7.76 14.23A5.78 5.78 0 0 0 12 16A5.78 5.78 0 0 0 16.24 14.23A5.78 5.78 0 0 0 18 10A5.78 5.78 0 0 0 16.24 5.74A5.78 5.78 0 0 0 12 4M20 10A8.04 8.04 0 0 1 19.43 12.8A7.84 7.84 0 0 1 18 15.28V23L12 21L6 23V15.28A7.9 7.9 0 0 1 4 10A7.68 7.68 0 0 1 6.33 4.36A7.73 7.73 0 0 1 12 2A7.73 7.73 0 0 1 17.67 4.36A7.68 7.68 0 0 1 20 10Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_about_log_foreground"/>
|
||||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_advanced_foreground"/>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Z"/>
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_appearance_foreground"/>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M480 880q-82 0-155-31.5t-127.5-86Q143 708 111.5 635T80 480q0-83 32.5-156t88-127Q256 143 330 111.5T488 80q80 0 151 27.5t124.5 76q53.5 48.5 85 115T880 442q0 115-70 176.5T640 680h-74q-9 0-12.5 5t-3.5 11q0 12 15 34.5t15 51.5q0 50-27.5 74T480 880Zm0-400Zm-220 40q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120-160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm200 0q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120 160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17ZM480 800q9 0 14.5-5t5.5-13q0-14-15-33t-15-57q0-42 29-67t71-25h70q66 0 113-38.5T800 442q0-121-92.5-201.5T488 160q-136 0-232 93t-96 227q0 133 93.5 226.5T480 800Z"/>
|
||||
</vector>
|
|
@ -1,14 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M480 880q-82 0-155-31.5t-127.5-86Q143 708 111.5 635T80 480q0-83 32.5-156t88-127Q256 143 330 111.5T488 80q80 0 151 27.5t124.5 76q53.5 48.5 85 115T880 442q0 115-70 176.5T640 680h-74q-9 0-12.5 5t-3.5 11q0 12 15 34.5t15 51.5q0 50-27.5 74T480 880Zm0-400Zm-220 40q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120-160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm200 0q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120 160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17ZM480 800q9 0 14.5-5t5.5-13q0-14-15-33t-15-57q0-42 29-67t71-25h70q66 0 113-38.5T800 442q0-121-92.5-201.5T488 160q-136 0-232 93t-96 227q0 133 93.5 226.5T480 800Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_correction_foreground"/>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M564,880L394,710L450,654L564,768L790,542L846,598L564,880ZM120,640L314,120L408,120L602,640L510,640L464,508L254,508L208,640L120,640ZM282,432L438,432L362,216L358,216L282,432Z"/>
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M564,880L394,710L450,654L564,768L790,542L846,598L564,880ZM120,640L314,120L408,120L602,640L510,640L464,508L254,508L208,640L120,640ZM282,432L438,432L362,216L358,216L282,432Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_gesture_foreground"/>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M554 842Q501 842 464 806 426 769 426 716 426 686 439 652 452 618 479 588 505 557 546 534 586 510 641 501 638 461 622 445 606 428 582 428 552 428 517 453 482 478 434 535 356 628 320 656 283 684 241 684 190 684 155 647 120 609 120 556 120 503 144 447 167 391 223 313 241 287 251 269 260 251 260 240 260 233 258 230 255 226 250 226 240 226 225 239 210 251 190 276L120 206Q152 167 185 147 218 126 250 126 297 126 329 159 360 191 360 238 360 267 345 302 330 336 295 384 257 438 239 479 220 520 220 549 220 566 226 575 231 584 241 584 253 584 275 564 296 543 361 467 424 392 475 360 526 328 582 328 659 328 698 379 736 430 741 496L840 496 840 596 741 596Q735 684 692 763 649 842 554 842ZM556 742Q595 742 615 698 634 653 640 603 584 617 555 652 526 686 526 712 526 726 534 734 542 742 556 742Z"/>
|
||||
</vector>
|
|
@ -1,14 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M554 842Q501 842 464 806 426 769 426 716 426 686 439 652 452 618 479 588 505 557 546 534 586 510 641 501 638 461 622 445 606 428 582 428 552 428 517 453 482 478 434 535 356 628 320 656 283 684 241 684 190 684 155 647 120 609 120 556 120 503 144 447 167 391 223 313 241 287 251 269 260 251 260 240 260 233 258 230 255 226 250 226 240 226 225 239 210 251 190 276L120 206Q152 167 185 147 218 126 250 126 297 126 329 159 360 191 360 238 360 267 345 302 330 336 295 384 257 438 239 479 220 520 220 549 220 566 226 575 231 584 241 584 253 584 275 564 296 543 361 467 424 392 475 360 526 328 582 328 659 328 698 379 736 430 741 496L840 496 840 596 741 596Q735 684 692 763 649 842 554 842ZM556 742Q595 742 615 698 634 653 640 603 584 617 555 652 526 686 526 712 526 726 534 734 542 742 556 742Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_languages_foreground"/>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M480 880q-82 0-155-31-73-32-127-86-55-55-86-128-32-73-32-155 0-83 32-155 31-73 86-127 54-55 127-86 73-32 155-32 83 0 156 32 72 31 127 86 54 54 86 127 31 72 31 155 0 82-31 155-32 73-86 128-55 54-127 86-73 31-156 31Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31-68-14-36-23-74H204q29 50 73 87 43 37 99 55Zm208 0q56-18 100-55 43-37 72-87H638q-9 38-22 74-14 35-32 68ZM170 560h136l-4-39q-2-20-2-41t2-40l4-40H170q-5 20-7 40-3 19-3 40t3 41q2 19 7 39Zm216 0h188l5-39 1-41-1-40-5-40H386l-4 40q-2 19-2 40t2 41l4 39Zm268 0h136l8-39q2-20 2-41t-2-40l-8-40H654l5 40 1 40-1 41-5 39Zm-16-240h118q-29-50-72-87-44-37-100-55 18 33 32 69 13 35 22 73Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 23-73 13-36 31-69-56 18-99 55-44 37-73 87Z"/>
|
||||
</vector>
|
|
@ -1,14 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
modified
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M480 880q-82 0-155-31-73-32-127-86-55-55-86-128-32-73-32-155 0-83 32-155 31-73 86-127 54-55 127-86 73-32 155-32 83 0 156 32 72 31 127 86 54 54 86 127 31 72 31 155 0 82-31 155-32 73-86 128-55 54-127 86-73 31-156 31Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31-68-14-36-23-74H204q29 50 73 87 43 37 99 55Zm208 0q56-18 100-55 43-37 72-87H638q-9 38-22 74-14 35-32 68ZM170 560h136l-4-39q-2-20-2-41t2-40l4-40H170q-5 20-7 40-3 19-3 40t3 41q2 19 7 39Zm216 0h188l5-39 1-41-1-40-5-40H386l-4 40q-2 19-2 40t2 41l4 39Zm268 0h136l8-39q2-20 2-41t-2-40l-8-40H654l5 40 1 40-1 41-5 39Zm-16-240h118q-29-50-72-87-44-37-100-55 18 33 32 69 13 35 22 73Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 23-73 13-36 31-69-56 18-99 55-44 37-73 87Z"/>
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_preferences_foreground"/>
|
||||
<!--
|
||||
icon from pictogrammers.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M8 12.14V2H6V12.14C4.28 12.59 3 14.14 3 16S4.28 19.41 6 19.86V22H8V19.86C9.72 19.41 11 17.86 11 16S9.72 12.59 8 12.14M7 14C8.1 14 9 14.9 9 16S8.1 18 7 18C5.9 18 5 17.1 5 16S5.9 14 7 14M18 2H16V4.14C14.28 4.59 13 6.14 13 8S14.28 11.41 16 11.86V22H18V11.86C19.72 11.41 21 9.86 21 8S19.72 4.59 18 4.14V2M17 6C18.1 6 19 6.9 19 8S18.1 10 17 10C15.9 10 15 9.1 15 8S15.9 6 17 6Z" />
|
||||
</vector>
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon from pictogrammers.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="32dp"
|
||||
android:width="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M8 12.14V2H6V12.14C4.28 12.59 3 14.14 3 16S4.28 19.41 6 19.86V22H8V19.86C9.72 19.41 11 17.86 11 16S9.72 12.59 8 12.14M7 14C8.1 14 9 14.9 9 16S8.1 18 7 18C5.9 18 5 17.1 5 16S5.9 14 7 14M18 2H16V4.14C14.28 4.59 13 6.14 13 8S14.28 11.41 16 11.86V22H18V11.86C19.72 11.41 21 9.86 21 8S19.72 4.59 18 4.14V2M17 6C18.1 6 19 6.9 19 8S18.1 10 17 10C15.9 10 15 9.1 15 8S15.9 6 17 6Z" />
|
||||
</vector>
|
|
@ -1,4 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="@dimen/settings_screen_icon_inset"
|
||||
android:drawable="@drawable/ic_settings_toolbar_foreground"/>
|
||||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="40dp"
|
||||
android:width="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!--
|
||||
icon available in Android Studio
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="40dp"
|
||||
android:width="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/foreground"
|
||||
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
|
||||
</vector>
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 The Android Open Source Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_focused="true"
|
||||
android:drawable="@color/setup_step_action_pressed" />
|
||||
<item
|
||||
android:state_pressed="true"
|
||||
android:drawable="@color/setup_step_action_pressed" />
|
||||
<item
|
||||
android:drawable="@color/setup_step_background" />
|
||||
</selector>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue