Merge branch 'new' into hangul_update

This commit is contained in:
Helium314 2023-09-16 10:48:06 +02:00
commit 9c091e7f31
8 changed files with 130 additions and 85 deletions

View file

@ -15,12 +15,31 @@ Might end up on F-Droid...
* additional dictionaries for emojis or scientific symbols can be used to provide suggestions ("emoji search") * additional dictionaries for emojis or scientific symbols can be used to provide suggestions ("emoji search")
* note that for Korean layouts, suggestions only work using [this dictionary](https://github.com/openboard-team/openboard/commit/83fca9533c03b9fecc009fc632577226bbd6301f), the tools in the dictionary repository are not able to create working dictionaries * note that for Korean layouts, suggestions only work using [this dictionary](https://github.com/openboard-team/openboard/commit/83fca9533c03b9fecc009fc632577226bbd6301f), the tools in the dictionary repository are not able to create working dictionaries
* Adjust keyboard themes (style and colors) * Adjust keyboard themes (style and colors)
* can follow the system's day/night setting * can follow the system's day/night setting on Android 10+ (and on some versions of Android 9)
* Split keyboard * Split keyboard (if the screen is large enough)
* Number row * Number row
* Number pad * Number pad
* Show all available extra characters on long pressing a key * Show all available extra characters on long pressing a key
## Hidden functionality
Features that may go unnoticed
* Long pressing the clipboard key (the optional one in suggestion strip) pastes system clipboard contents
* Long-press comma to access clipboard view, emoji view, one-handed mode, settings, or switch language
* emoji view and language switch will disappear if you have the corresponding key enabled
* for some layouts it's not the comma-key, but the key at the same position (e.g. it's `q` for Dvorak layout)
* Sliding key input: swipe from shift to another key to type a single uppercase key
* also works for the `?123` key to type a single symbol from the symbols keyboard, and for related keys
* Long-press a suggestion to show more suggestions, and a delete button to remove this suggestion
* Swipe up from a suggestion to open more suggestions, and release on the suggestion to select it
* You can add dictionaries by opening them in a file explorer
* only works with content-uris and not with file-uris, meaning that it may not work with some file explorers
* Debug APK only
* Long-press a suggestion to show the source dictionary
* Debug settings in advanced preferences, though not very useful except for dumping dictionaries into the log
* When the app crashes, you will be asked whether you want crash logs when you open the settings
* For users doing manual backups with root access: starting at Android 7, the shared preferences file is not in the default location, because the app is using [device protected storage](https://developer.android.com/reference/android/content/Context#createDeviceProtectedStorageContext()). This is necessary so the settings can be read before the device is unlocked, e.g. at boot.
* file is located in `/data/user_de/0/<package_id>/shared_prefs/`, though this may depend on the device and Android version
## Important differences and changes to OpenBoard ## Important differences and changes to OpenBoard
* Debug version can be installed along OpenBoard * Debug version can be installed along OpenBoard
* Allow users to add and replace built-in dictionaries * Allow users to add and replace built-in dictionaries

View file

@ -124,6 +124,8 @@ public class KeyboardView extends View {
private Bitmap mOffscreenBuffer; private Bitmap mOffscreenBuffer;
/** Flag for whether the key hints should be displayed */ /** Flag for whether the key hints should be displayed */
private boolean mShowsHints; private boolean mShowsHints;
/** Scale for downscaling icons and fixed size backgrounds if keyboard height is set below 80% */
private float mIconScaleFactor;
/** The canvas for the above mutable keyboard bitmap */ /** The canvas for the above mutable keyboard bitmap */
@NonNull @NonNull
private final Canvas mOffscreenCanvas = new Canvas(); private final Canvas mOffscreenCanvas = new Canvas();
@ -301,6 +303,8 @@ public class KeyboardView extends View {
} }
mShowsHints = Settings.getInstance().getCurrent().mShowsHints; mShowsHints = Settings.getInstance().getCurrent().mShowsHints;
final float scale = Settings.getInstance().getCurrent().mKeyboardHeightScale;
mIconScaleFactor = scale < 0.8f ? scale + 0.2f : scale;
final Paint paint = mPaint; final Paint paint = mPaint;
final Drawable background = getBackground(); final Drawable background = getBackground();
// Calculate clip region and set. // Calculate clip region and set.
@ -370,8 +374,8 @@ public class KeyboardView extends View {
if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags) if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)
// HACK: To disable expanding normal/functional key background. // HACK: To disable expanding normal/functional key background.
&& !key.hasCustomActionLabel()) { && !key.hasCustomActionLabel()) {
bgWidth = background.getIntrinsicWidth(); bgWidth = (int) (background.getIntrinsicWidth() * mIconScaleFactor);
bgHeight = background.getIntrinsicHeight(); bgHeight = (int) (background.getIntrinsicHeight() * mIconScaleFactor);
bgX = (keyWidth - bgWidth) / 2; bgX = (keyWidth - bgWidth) / 2;
bgY = (keyHeight - bgHeight) / 2; bgY = (keyHeight - bgHeight) / 2;
} else { } else {
@ -412,8 +416,9 @@ public class KeyboardView extends View {
labelBaseline = centerY + labelCharHeight / 2.0f; labelBaseline = centerY + labelCharHeight / 2.0f;
// Horizontal label text alignment // Horizontal label text alignment
if (key.isAlignLabelOffCenter()) { if (key.isAlignLabelOffCenter() && mShowsHints) {
// The label is placed off center of the key. Used mainly on "phone number" layout. // The label is placed off center of the key. Currently used only on "phone number" layout
// to have letter hints shown nicely. We don't want to align it off center if hints are off.
labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth; labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth;
paint.setTextAlign(Align.LEFT); paint.setTextAlign(Align.LEFT);
} else { } else {
@ -496,11 +501,11 @@ public class KeyboardView extends View {
if (label == null && icon != null) { if (label == null && icon != null) {
final int iconWidth; final int iconWidth;
if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) { if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio); iconWidth = (int) (keyWidth * mSpacebarIconWidthRatio * mIconScaleFactor);
} else { } else {
iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth); iconWidth = (int) (Math.min(icon.getIntrinsicWidth(), keyWidth) * mIconScaleFactor);
} }
final int iconHeight = icon.getIntrinsicHeight(); final int iconHeight = (int) (icon.getIntrinsicHeight() * mIconScaleFactor);
final int iconY; final int iconY;
if (key.isAlignIconToBottom()) { if (key.isAlignIconToBottom()) {
iconY = keyHeight - iconHeight; iconY = keyHeight - iconHeight;

View file

@ -153,7 +153,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
/** /**
* The locale associated with the dictionary group. * The locale associated with the dictionary group.
*/ */
@Nullable public final Locale mLocale; @NonNull public final Locale mLocale;
/** /**
* The user account associated with the dictionary group. * The user account associated with the dictionary group.
@ -202,10 +202,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
public DictionaryGroup() { public DictionaryGroup() {
this(null /* locale */, null /* mainDict */, null /* account */, Collections.emptyMap() /* subDicts */); this(new Locale(""), null /* mainDict */, null /* account */, Collections.emptyMap() /* subDicts */);
} }
public DictionaryGroup(@Nullable final Locale locale, public DictionaryGroup(@NonNull final Locale locale,
@Nullable final Dictionary mainDict, @Nullable final Dictionary mainDict,
@Nullable final String account, @Nullable final String account,
@NonNull final Map<String, ExpandableBinaryDictionary> subDicts) { @NonNull final Map<String, ExpandableBinaryDictionary> subDicts) {
@ -288,7 +288,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
@Override @Override
public boolean isActive() { public boolean isActive() {
return mDictionaryGroups.get(0).mLocale != null; return !mDictionaryGroups.get(0).mLocale.getLanguage().isEmpty();
} }
// used in // used in
@ -340,12 +340,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
@Nullable @Nullable
static DictionaryGroup findDictionaryGroupWithLocale(final List<DictionaryGroup> dictionaryGroups, static DictionaryGroup findDictionaryGroupWithLocale(final List<DictionaryGroup> dictionaryGroups,
final Locale locale) { @NonNull final Locale locale) {
if (dictionaryGroups == null) return null; if (dictionaryGroups == null) return null;
for (DictionaryGroup dictionaryGroup : dictionaryGroups) { for (DictionaryGroup dictionaryGroup : dictionaryGroups) {
if (locale == null && dictionaryGroup.mLocale == null) if (locale.equals(dictionaryGroup.mLocale))
return dictionaryGroup;
if (locale != null && locale.equals(dictionaryGroup.mLocale))
return dictionaryGroup; return dictionaryGroup;
} }
return null; return null;
@ -354,7 +352,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
// original // original
public void resetDictionaries( public void resetDictionaries(
final Context context, final Context context,
final Locale newLocale, @NonNull final Locale newLocale,
final boolean useContactsDict, final boolean useContactsDict,
final boolean usePersonalizedDicts, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary, final boolean forceReloadMainDictionary,
@ -399,10 +397,8 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
final ArrayList<DictionaryGroup> newDictionaryGroups = new ArrayList<>(allLocales.size()); final ArrayList<DictionaryGroup> newDictionaryGroups = new ArrayList<>(allLocales.size());
for (Locale locale : allLocales) { for (Locale locale : allLocales) {
// get existing dictionary group for new locale // get existing dictionary group for new locale
final DictionaryGroup oldDictionaryGroupForLocale = final DictionaryGroup oldDictionaryGroupForLocale = findDictionaryGroupWithLocale(mDictionaryGroups, locale);
findDictionaryGroupWithLocale(mDictionaryGroups, locale); final ArrayList<String> dictTypesToCleanupForLocale = existingDictionariesToCleanup.get(locale);
final ArrayList<String> dictTypesToCleanupForLocale =
existingDictionariesToCleanup.get(locale);
final boolean noExistingDictsForThisLocale = (null == oldDictionaryGroupForLocale); final boolean noExistingDictsForThisLocale = (null == oldDictionaryGroupForLocale);
// create new or re-use already loaded main dict // create new or re-use already loaded main dict
@ -542,13 +538,13 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
final File dictFile = dictionaryFiles.get(dictType); final File dictFile = dictionaryFiles.get(dictType);
final ExpandableBinaryDictionary dict = getSubDict( final ExpandableBinaryDictionary dict = getSubDict(
dictType, context, locale, dictFile, "" /* dictNamePrefix */, account); dictType, context, locale, dictFile, "" /* dictNamePrefix */, account);
if (dict == null) {
throw new RuntimeException("Unknown dictionary type: " + dictType);
}
if (additionalDictAttributes.containsKey(dictType)) { if (additionalDictAttributes.containsKey(dictType)) {
dict.clearAndFlushDictionaryWithAdditionalAttributes( dict.clearAndFlushDictionaryWithAdditionalAttributes(
additionalDictAttributes.get(dictType)); additionalDictAttributes.get(dictType));
} }
if (dict == null) {
throw new RuntimeException("Unknown dictionary type: " + dictType);
}
dict.reloadDictionaryIfRequired(); dict.reloadDictionaryIfRequired();
dict.waitAllTasksForTests(); dict.waitAllTasksForTests();
subDicts.put(dictType, dict); subDicts.put(dictType, dict);
@ -939,9 +935,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
if (TextUtils.isEmpty(word)) { if (TextUtils.isEmpty(word)) {
return false; return false;
} }
if (dictionaryGroup.mLocale == null) {
return false;
}
if (isBlacklisted(word)) return false; if (isBlacklisted(word)) return false;
for (final String dictType : dictionariesToCheck) { for (final String dictType : dictionariesToCheck) {
final Dictionary dictionary = dictionaryGroup.getDict(dictType); final Dictionary dictionary = dictionaryGroup.getDict(dictType);
@ -992,7 +985,8 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
} else isInContacts = false; } else isInContacts = false;
// add to blacklist if in main or contacts dictionaries // add to blacklist if in main or contacts dictionaries
if ((isInContacts || group.getDict(Dictionary.TYPE_MAIN).isValidWord(word)) && group.blacklist.add(word)) { if ((isInContacts || (group.hasDict(Dictionary.TYPE_MAIN, null) && group.getDict(Dictionary.TYPE_MAIN).isValidWord(word)))
&& group.blacklist.add(word)) {
// write to file if word wasn't already in blacklist // write to file if word wasn't already in blacklist
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> { ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(() -> {
try { try {

View file

@ -110,18 +110,22 @@ class KeyboardWrapperView @JvmOverloads constructor(
keyboardView.measuredHeight keyboardView.measuredHeight
) )
val scale = Settings.getInstance().current.mKeyboardHeightScale
// scale one-handed mode button height if keyboard height scale is < 80%
// more relevant: also change the distance, so the buttons are actually visible
val heightScale = scale + 0.2f
val buttonsLeft = if (isLeftGravity) keyboardView.measuredWidth else 0 val buttonsLeft = if (isLeftGravity) keyboardView.measuredWidth else 0
stopOneHandedModeBtn.layout( stopOneHandedModeBtn.layout(
buttonsLeft + (spareWidth - stopOneHandedModeBtn.measuredWidth) / 2, buttonsLeft + (spareWidth - stopOneHandedModeBtn.measuredWidth) / 2,
stopOneHandedModeBtn.measuredHeight / 2, (heightScale * stopOneHandedModeBtn.measuredHeight / 2).toInt(),
buttonsLeft + (spareWidth + stopOneHandedModeBtn.measuredWidth) / 2, buttonsLeft + (spareWidth + stopOneHandedModeBtn.measuredWidth) / 2,
3 * stopOneHandedModeBtn.measuredHeight / 2 (heightScale * 3 * stopOneHandedModeBtn.measuredHeight / 2).toInt()
) )
switchOneHandedModeBtn.layout( switchOneHandedModeBtn.layout(
buttonsLeft + (spareWidth - switchOneHandedModeBtn.measuredWidth) / 2, buttonsLeft + (spareWidth - switchOneHandedModeBtn.measuredWidth) / 2,
2 * stopOneHandedModeBtn.measuredHeight, (heightScale * 2 * stopOneHandedModeBtn.measuredHeight).toInt(),
buttonsLeft + (spareWidth + switchOneHandedModeBtn.measuredWidth) / 2, buttonsLeft + (spareWidth + switchOneHandedModeBtn.measuredWidth) / 2,
2 * stopOneHandedModeBtn.measuredHeight + switchOneHandedModeBtn.measuredHeight (heightScale * (2 * stopOneHandedModeBtn.measuredHeight + switchOneHandedModeBtn.measuredHeight)).toInt()
) )
} }

View file

@ -742,7 +742,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param locale the locale * @param locale the locale
*/ */
// TODO: make sure the current settings always have the right locales, and read from them. // TODO: make sure the current settings always have the right locales, and read from them.
private void resetDictionaryFacilitator(final Locale locale) { private void resetDictionaryFacilitator(@NonNull final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent(); final SettingsValues settingsValues = mSettings.getCurrent();
mDictionaryFacilitator.resetDictionaries(this /* context */, locale, mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
settingsValues.mUseContactsDictionary, settingsValues.mUsePersonalizedDicts, settingsValues.mUseContactsDictionary, settingsValues.mUsePersonalizedDicts,

View file

@ -269,7 +269,6 @@ public class SuggestedWords {
public final int mScore; public final int mScore;
public final int mKindAndFlags; public final int mKindAndFlags;
public final int mCodePointCount; public final int mCodePointCount;
@Deprecated
public final Dictionary mSourceDict; public final Dictionary mSourceDict;
// For auto-commit. This keeps track of the index inside the touch coordinates array // For auto-commit. This keeps track of the index inside the touch coordinates array
// passed to native code to get suggestions for a gesture that corresponds to the first // passed to native code to get suggestions for a gesture that corresponds to the first

View file

@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.get
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.size import androidx.core.view.size
@ -15,6 +16,8 @@ import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
import org.dslul.openboard.inputmethod.latin.databinding.LanguageListItemBinding
import org.dslul.openboard.inputmethod.latin.databinding.LocaleSettingsDialogBinding
import org.dslul.openboard.inputmethod.latin.utils.* import org.dslul.openboard.inputmethod.latin.utils.*
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -24,26 +27,28 @@ class LanguageSettingsDialog(
private val infos: MutableList<SubtypeInfo>, private val infos: MutableList<SubtypeInfo>,
private val fragment: LanguageSettingsFragment?, private val fragment: LanguageSettingsFragment?,
private val onlySystemLocales: Boolean, private val onlySystemLocales: Boolean,
private val onSubtypesChanged: () -> Unit private val reloadSetting: () -> Unit
) : AlertDialog(context), LanguageSettingsFragment.Listener { ) : AlertDialog(context), LanguageSettingsFragment.Listener {
private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!! private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!!
private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null) private val binding = LocaleSettingsDialogBinding.inflate(LayoutInflater.from(context))
private val mainLocaleString = infos.first().subtype.locale() private val mainLocaleString = infos.first().subtype.locale()
private val mainLocale = mainLocaleString.toLocale() private val mainLocale = mainLocaleString.toLocale()
private var hasInternalDictForLanguage = false
private lateinit var userDicts: MutableSet<File>
init { init {
setTitle(infos.first().displayName) setTitle(infos.first().displayName)
setView(ScrollView(context).apply { addView(view) }) setView(ScrollView(context).apply { addView(binding.root) })
setButton(BUTTON_NEGATIVE, context.getString(R.string.dialog_close)) { _, _ -> setButton(BUTTON_NEGATIVE, context.getString(R.string.dialog_close)) { _, _ ->
dismiss() dismiss()
} }
if (onlySystemLocales) if (onlySystemLocales)
view.findViewById<View>(R.id.subtypes).isGone = true binding.subtypes.isGone = true
else else
fillSubtypesView(view.findViewById(R.id.subtypes)) fillSubtypesView()
fillSecondaryLocaleView(view.findViewById(R.id.secondary_languages)) fillSecondaryLocaleView()
fillDictionariesView(view.findViewById(R.id.dictionaries)) fillDictionariesView()
} }
override fun onStart() { override fun onStart() {
@ -56,9 +61,9 @@ class LanguageSettingsDialog(
fragment?.setListener(null) fragment?.setListener(null)
} }
private fun fillSubtypesView(subtypesView: LinearLayout) { private fun fillSubtypesView() {
if (infos.any { it.subtype.isAsciiCapable }) { // currently can only add subtypes for latin keyboards if (infos.any { it.subtype.isAsciiCapable }) { // currently can only add subtypes for latin keyboards
subtypesView.findViewById<ImageView>(R.id.add_subtype).setOnClickListener { binding.addSubtype.setOnClickListener {
val layouts = context.resources.getStringArray(R.array.predefined_layouts) val layouts = context.resources.getStringArray(R.array.predefined_layouts)
.filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } } .filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } }
val displayNames = layouts.map { SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) } val displayNames = layouts.map { SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) }
@ -70,23 +75,23 @@ class LanguageSettingsDialog(
val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default, because why else add them val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default, because why else add them
addAdditionalSubtype(prefs, context.resources, newSubtype) addAdditionalSubtype(prefs, context.resources, newSubtype)
addEnabledSubtype(prefs, newSubtype) addEnabledSubtype(prefs, newSubtype)
addSubtypeToView(newSubtypeInfo, subtypesView) addSubtypeToView(newSubtypeInfo)
infos.add(newSubtypeInfo) infos.add(newSubtypeInfo)
onSubtypesChanged() reloadSetting()
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
} }
} else } else
subtypesView.findViewById<View>(R.id.add_subtype).isGone = true binding.addSubtype.isGone = true
// add subtypes // add subtypes
infos.sortedBy { it.displayName }.forEach { infos.sortedBy { it.displayName }.forEach {
addSubtypeToView(it, subtypesView) addSubtypeToView(it)
} }
} }
private fun addSubtypeToView(subtype: SubtypeInfo, subtypesView: LinearLayout) { private fun addSubtypeToView(subtype: SubtypeInfo) {
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView) val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
row.findViewById<TextView>(R.id.language_name).text = row.findViewById<TextView>(R.id.language_name).text =
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype) SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype)
@ -104,7 +109,7 @@ class LanguageSettingsDialog(
else else
removeEnabledSubtype(prefs, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype)
subtype.isEnabled = b subtype.isEnabled = b
onSubtypesChanged() reloadSetting()
} }
} }
if (isAdditionalSubtype(subtype.subtype)) { if (isAdditionalSubtype(subtype.subtype)) {
@ -113,19 +118,19 @@ class LanguageSettingsDialog(
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
// can be re-added easily, no need for confirmation dialog // can be re-added easily, no need for confirmation dialog
subtypesView.removeView(row) binding.subtypes.removeView(row)
infos.remove(subtype) infos.remove(subtype)
removeAdditionalSubtype(prefs, context.resources, subtype.subtype) removeAdditionalSubtype(prefs, context.resources, subtype.subtype)
removeEnabledSubtype(prefs, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype)
onSubtypesChanged() reloadSetting()
} }
} }
} }
subtypesView.addView(row) binding.subtypes.addView(row)
} }
private fun fillSecondaryLocaleView(secondaryLocalesView: LinearLayout) { private fun fillSecondaryLocaleView() {
// can only use multilingual typing if there is more than one dictionary available // can only use multilingual typing if there is more than one dictionary available
val availableSecondaryLocales = getAvailableSecondaryLocales( val availableSecondaryLocales = getAvailableSecondaryLocales(
context, context,
@ -134,10 +139,10 @@ class LanguageSettingsDialog(
) )
val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString) val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString)
selectedSecondaryLocales.forEach { selectedSecondaryLocales.forEach {
addSecondaryLocaleView(it, secondaryLocalesView) addSecondaryLocaleView(it)
} }
if (availableSecondaryLocales.isNotEmpty()) { if (availableSecondaryLocales.isNotEmpty()) {
secondaryLocalesView.findViewById<ImageView>(R.id.add_secondary_language).apply { binding.addSecondaryLanguage.apply {
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
val locales = (availableSecondaryLocales - Settings.getSecondaryLocales(prefs, mainLocaleString)).sortedBy { it.displayName } val locales = (availableSecondaryLocales - Settings.getSecondaryLocales(prefs, mainLocaleString)).sortedBy { it.displayName }
@ -148,35 +153,37 @@ class LanguageSettingsDialog(
val locale = locales[i] val locale = locales[i]
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() }
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings + locale.toString()) Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings + locale.toString())
addSecondaryLocaleView(locale, secondaryLocalesView) addSecondaryLocaleView(locale)
di.dismiss() di.dismiss()
reloadSetting()
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
} }
} }
} else if (selectedSecondaryLocales.isEmpty()) } else if (selectedSecondaryLocales.isEmpty())
secondaryLocalesView.isGone = true binding.secondaryLocales.isGone = true
} }
private fun addSecondaryLocaleView(locale: Locale, secondaryLocalesView: LinearLayout) { private fun addSecondaryLocaleView(locale: Locale) {
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView) val rowBinding = LanguageListItemBinding.inflate(LayoutInflater.from(context), listView, false)
row.findViewById<Switch>(R.id.language_switch).isGone = true rowBinding.languageSwitch.isGone = true
row.findViewById<Switch>(R.id.language_details).isGone = true rowBinding.languageDetails.isGone = true
row.findViewById<TextView>(R.id.language_name).text = locale.displayName rowBinding.languageName.text = locale.displayName
row.findViewById<ImageView>(R.id.delete_button).apply { rowBinding.deleteButton.apply {
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() }
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString()) Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString())
secondaryLocalesView.removeView(row) binding.secondaryLocales.removeView(rowBinding.root)
reloadSetting()
} }
} }
secondaryLocalesView.addView(row) binding.secondaryLocales.addView(rowBinding.root)
} }
private fun fillDictionariesView(dictionariesView: LinearLayout) { private fun fillDictionariesView() {
dictionariesView.findViewById<ImageView>(R.id.add_dictionary).setOnClickListener { binding.addDictionary.setOnClickListener {
val link = "<a href='$DICTIONARY_URL'>" + context.getString(R.string.dictionary_link_text) + "</a>" val link = "<a href='$DICTIONARY_URL'>" + context.getString(R.string.dictionary_link_text) + "</a>"
val message = SpannableStringUtils.fromHtml(context.getString(R.string.add_dictionary, link)) val message = SpannableStringUtils.fromHtml(context.getString(R.string.add_dictionary, link))
val dialog = Builder(context) val dialog = Builder(context)
@ -188,9 +195,11 @@ class LanguageSettingsDialog(
dialog.show() dialog.show()
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance() (dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance()
} }
val (userDicts, hasInternalDictForLanguage) = getUserAndInternalDictionaries(context, mainLocaleString) val (_userDicts, _hasInternalDictForLanguage) = getUserAndInternalDictionaries(context, mainLocaleString)
userDicts = _userDicts.toMutableSet()
hasInternalDictForLanguage = _hasInternalDictForLanguage
if (hasInternalDictForLanguage) { if (hasInternalDictForLanguage) {
dictionariesView.addView(TextView(context, null, R.style.PreferenceCategoryTitleText).apply { binding.dictionaries.addView(TextView(context, null, R.style.PreferenceCategoryTitleText).apply {
setText(R.string.internal_dictionary_summary) setText(R.string.internal_dictionary_summary)
textSize *= 0.8f textSize *= 0.8f
setPadding((context.resources.displayMetrics.scaledDensity * 16).toInt(), 0, 0, 0) setPadding((context.resources.displayMetrics.scaledDensity * 16).toInt(), 0, 0, 0)
@ -198,25 +207,31 @@ class LanguageSettingsDialog(
}) })
} }
userDicts.sorted().forEach { userDicts.sorted().forEach {
addDictionaryToView(it, dictionariesView) addDictionaryToView(it)
} }
} }
override fun onNewDictionary(uri: Uri?) { override fun onNewDictionary(uri: Uri?) {
NewDictionaryAdder(context) { replaced, dictFile -> NewDictionaryAdder(context) { replaced, dictFile ->
if (!replaced) if (!replaced) {
addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries)) 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) }.addDictionary(uri, mainLocale)
} }
private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) { private fun addDictionaryToView(dictFile: File) {
if (!infos.first().hasDictionary) { if (!infos.first().hasDictionary) {
infos.forEach { it.hasDictionary = true } infos.forEach { it.hasDictionary = true }
} }
val dictType = dictFile.name.substringBefore("_${USER_DICTIONARY_SUFFIX}") val dictType = dictFile.name.substringBefore("_${USER_DICTIONARY_SUFFIX}")
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView) val rowBinding = LanguageListItemBinding.inflate(LayoutInflater.from(context), listView, false)
row.findViewById<TextView>(R.id.language_name).text = dictType rowBinding.languageName.text = dictType
row.findViewById<TextView>(R.id.language_details).apply { rowBinding.languageDetails.apply {
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length()) val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())
if (header?.description == null) { if (header?.description == null) {
isGone = true isGone = true
@ -225,8 +240,8 @@ class LanguageSettingsDialog(
text = header.description text = header.description
} }
} }
row.findViewById<Switch>(R.id.language_switch).isGone = true rowBinding.languageSwitch.isGone = true
row.findViewById<ImageView>(R.id.delete_button).apply { rowBinding.deleteButton.apply {
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
confirmDialog(context, context.getString(R.string.remove_dictionary_message, dictType), context.getString( confirmDialog(context, context.getString(R.string.remove_dictionary_message, dictType), context.getString(
@ -237,15 +252,19 @@ class LanguageSettingsDialog(
parent.delete() parent.delete()
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
fragment?.activity?.sendBroadcast(newDictBroadcast) fragment?.activity?.sendBroadcast(newDictBroadcast)
dictionariesView.removeView(row) binding.dictionaries.removeView(rowBinding.root)
if (dictionariesView.size < 2) { // first view is "Dictionaries" if (binding.dictionaries.size < 2) { // first view is "Dictionaries"
infos.forEach { it.hasDictionary = false } 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}" }
} }
} }
} }
dictionariesView.addView(row) }
binding.dictionaries.addView(rowBinding.root)
} }
} }

View file

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:orientation="vertical"
android:showDividers="middle" android:showDividers="middle"
android:divider="@drawable/ic_divider" android:divider="@drawable/ic_divider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp"> android:padding="10dp">
<!-- layout appears unnecessary, but more views will be added -->
<LinearLayout <LinearLayout
android:id="@+id/subtypes" android:id="@+id/subtypes"
android:orientation="vertical" android:orientation="vertical"
@ -15,7 +17,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:orientation="horizontal"> android:orientation="horizontal"
tools:ignore="UseCompoundDrawables"> <!-- view gets an onClickListener -->
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -31,8 +34,9 @@
android:src="@drawable/ic_plus" /> android:src="@drawable/ic_plus" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<!-- layout appears unnecessary, but more views will be added -->
<LinearLayout <LinearLayout
android:id="@+id/secondary_languages" android:id="@+id/secondary_locales"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -57,6 +61,7 @@
android:src="@drawable/ic_plus" /> android:src="@drawable/ic_plus" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<!-- layout appears unnecessary, but more views will be added -->
<LinearLayout <LinearLayout
android:id="@+id/dictionaries" android:id="@+id/dictionaries"
android:orientation="vertical" android:orientation="vertical"