mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-22 07:09:10 +00:00
add layout screen for choosing and editing default (non-main) layouts
This commit is contained in:
parent
6b86ea236b
commit
a3e85bc664
24 changed files with 478 additions and 82 deletions
|
@ -17,11 +17,10 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.TextKeyData
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSelector
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSelector
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey
|
||||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
import helium314.keyboard.latin.settings.Defaults.default
|
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
||||||
import helium314.keyboard.latin.utils.LayoutType
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
import helium314.keyboard.latin.utils.LayoutUtils
|
||||||
import helium314.keyboard.latin.utils.Log
|
import helium314.keyboard.latin.utils.Log
|
||||||
import helium314.keyboard.latin.utils.getCustomLayoutFiles
|
import helium314.keyboard.latin.utils.getCustomLayoutFiles
|
||||||
import helium314.keyboard.latin.utils.prefs
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
@ -40,7 +39,7 @@ object LayoutParser {
|
||||||
if (layoutType == LayoutType.FUNCTIONAL && !params.mId.isAlphaOrSymbolKeyboard)
|
if (layoutType == LayoutType.FUNCTIONAL && !params.mId.isAlphaOrSymbolKeyboard)
|
||||||
return mutableListOf(mutableListOf()) // no functional keys
|
return mutableListOf(mutableListOf()) // no functional keys
|
||||||
val layoutName = if (layoutType == LayoutType.MAIN) params.mId.mSubtype.mainLayoutName
|
val layoutName = if (layoutType == LayoutType.MAIN) params.mId.mSubtype.mainLayoutName
|
||||||
else params.mId.mSubtype.layouts[layoutType] ?: Settings.getLayoutName(layoutType, context.prefs())
|
else params.mId.mSubtype.layouts[layoutType] ?: Settings.readDefaultLayoutName(layoutType, context.prefs())
|
||||||
return layoutCache.getOrPut(layoutType.name + layoutName) {
|
return layoutCache.getOrPut(layoutType.name + layoutName) {
|
||||||
createCacheLambda(layoutType, layoutName, context)
|
createCacheLambda(layoutType, layoutName, context)
|
||||||
}(params)
|
}(params)
|
||||||
|
@ -105,11 +104,7 @@ object LayoutParser {
|
||||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX))
|
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX))
|
||||||
getCustomLayoutFiles(layoutType, context)
|
getCustomLayoutFiles(layoutType, context)
|
||||||
.firstOrNull { it.name.startsWith(layoutName) }?.let { return it.readText() }
|
.firstOrNull { it.name.startsWith(layoutName) }?.let { return it.readText() }
|
||||||
val layouts = context.assets.list(layoutType.folder)!!
|
return LayoutUtils.getContent(layoutType, layoutName, context)
|
||||||
layouts.firstOrNull { it.startsWith("$layoutName.") }
|
|
||||||
?.let { return context.assets.open(layoutType.folder + it).reader().readText() }
|
|
||||||
val fallback = layouts.first { it.startsWith(layoutType.default) } // must exist!
|
|
||||||
return context.assets.open(layoutType.folder + fallback).reader().readText()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow commenting lines by starting them with "//"
|
// allow commenting lines by starting them with "//"
|
||||||
|
|
|
@ -41,7 +41,7 @@ object Defaults {
|
||||||
LayoutType.PHONE -> "phone"
|
LayoutType.PHONE -> "phone"
|
||||||
LayoutType.PHONE_SYMBOLS -> "phone_symbols"
|
LayoutType.PHONE_SYMBOLS -> "phone_symbols"
|
||||||
LayoutType.EMOJI_BOTTOM -> "emoji_bottom_row"
|
LayoutType.EMOJI_BOTTOM -> "emoji_bottom_row"
|
||||||
LayoutType.CLIPBOARD_BOTTOM -> "clipboard_bottom_row"
|
LayoutType.CLIPBOARD_BOTTOM -> "clip_bottom_row"
|
||||||
}
|
}
|
||||||
|
|
||||||
const val PREF_THEME_STYLE = KeyboardTheme.STYLE_MATERIAL
|
const val PREF_THEME_STYLE = KeyboardTheme.STYLE_MATERIAL
|
||||||
|
|
|
@ -209,7 +209,7 @@ class LanguageSettingsDialog(
|
||||||
reloadSetting()
|
reloadSetting()
|
||||||
}
|
}
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
confirmDialog(context, context.getString(R.string.delete_layout, getLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
confirmDialog(context, context.getString(R.string.delete_layout, getCustomLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
||||||
} else {
|
} else {
|
||||||
delete()
|
delete()
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,10 +551,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
return new File(DeviceProtectedUtils.getFilesDir(context), "custom_font");
|
return new File(DeviceProtectedUtils.getFilesDir(context), "custom_font");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLayoutName(final LayoutType type, final SharedPreferences prefs) {
|
// "default" layout as in this is used if nothing else is specified in the subtype
|
||||||
|
public static String readDefaultLayoutName(final LayoutType type, final SharedPreferences prefs) {
|
||||||
return prefs.getString(PREF_LAYOUT_PREFIX + type.name(), Defaults.INSTANCE.getDefault(type));
|
return prefs.getString(PREF_LAYOUT_PREFIX + type.name(), Defaults.INSTANCE.getDefault(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeDefaultLayoutName(final String name, final LayoutType type, final SharedPreferences prefs) {
|
||||||
|
prefs.edit().putString(PREF_LAYOUT_PREFIX + type.name(), name).apply();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Typeface getCustomTypeface() {
|
public Typeface getCustomTypeface() {
|
||||||
if (!sCustomTypefaceLoaded) {
|
if (!sCustomTypefaceLoaded) {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import helium314.keyboard.latin.R;
|
||||||
import helium314.keyboard.latin.RichInputMethodManager;
|
import helium314.keyboard.latin.RichInputMethodManager;
|
||||||
import helium314.keyboard.latin.common.Colors;
|
import helium314.keyboard.latin.common.Colors;
|
||||||
import helium314.keyboard.latin.permissions.PermissionsUtil;
|
import helium314.keyboard.latin.permissions.PermissionsUtil;
|
||||||
import helium314.keyboard.latin.utils.CustomLayoutUtilsKt;
|
|
||||||
import helium314.keyboard.latin.utils.InputTypeUtils;
|
import helium314.keyboard.latin.utils.InputTypeUtils;
|
||||||
import helium314.keyboard.latin.utils.JniUtils;
|
import helium314.keyboard.latin.utils.JniUtils;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
|
|
@ -79,6 +79,7 @@ public final class AdditionalSubtypeUtils {
|
||||||
return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true);
|
return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: adjust so we can store more stuff in extra values
|
||||||
private static String getPrefSubtype(final InputMethodSubtype subtype) {
|
private static String getPrefSubtype(final InputMethodSubtype subtype) {
|
||||||
final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
|
final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
|
||||||
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName;
|
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName;
|
||||||
|
@ -121,7 +122,7 @@ public final class AdditionalSubtypeUtils {
|
||||||
final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN);
|
final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN);
|
||||||
// Here we assume that all the additional subtypes are EmojiCapable
|
// Here we assume that all the additional subtypes are EmojiCapable
|
||||||
final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable);
|
final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable);
|
||||||
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !keyboardLayoutSetName.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX)) {
|
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !LayoutUtilsCustomKt.isCustomLayout(keyboardLayoutSetName)) {
|
||||||
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
||||||
// layout has been removed.
|
// layout has been removed.
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package helium314.keyboard.latin.utils
|
package helium314.keyboard.latin.utils
|
||||||
|
|
||||||
|
import helium314.keyboard.latin.R
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.EnumMap
|
import java.util.EnumMap
|
||||||
|
|
||||||
|
@ -20,5 +21,20 @@ enum class LayoutType {
|
||||||
}
|
}
|
||||||
|
|
||||||
val LayoutType.folder get() = "layouts${File.separator}${name.lowercase()}${File.separator}"
|
val LayoutType.folder get() = "layouts${File.separator}${name.lowercase()}${File.separator}"
|
||||||
|
|
||||||
|
val LayoutType.displayNameId get() = when (this) {
|
||||||
|
MAIN -> TODO()
|
||||||
|
SYMBOLS -> R.string.layout_symbols
|
||||||
|
MORE_SYMBOLS -> R.string.layout_symbols_shifted
|
||||||
|
FUNCTIONAL -> R.string.layout_functional_keys
|
||||||
|
NUMBER -> R.string.layout_number
|
||||||
|
NUMBER_ROW -> R.string.layout_number_row
|
||||||
|
NUMPAD -> R.string.layout_numpad
|
||||||
|
NUMPAD_LANDSCAPE -> R.string.layout_numpad_landscape
|
||||||
|
PHONE -> R.string.layout_phone
|
||||||
|
PHONE_SYMBOLS -> R.string.layout_phone_symbols
|
||||||
|
EMOJI_BOTTOM -> R.string.layout_emoji_bottom_row
|
||||||
|
CLIPBOARD_BOTTOM -> R.string.layout_clip_bottom_row
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package helium314.keyboard.latin.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import helium314.keyboard.latin.settings.Defaults.default
|
||||||
|
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
||||||
|
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
// for layouts provided by the app
|
||||||
|
object LayoutUtils {
|
||||||
|
fun getAvailableLayouts(layoutType: LayoutType, context: Context, locale: Locale? = null): Collection<String> {
|
||||||
|
if (layoutType != LayoutType.MAIN)
|
||||||
|
return context.assets.list(layoutType.folder)?.map { it.substringBefore(".") }.orEmpty()
|
||||||
|
if (locale == null)
|
||||||
|
return getAllAvailableSubtypes().mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
|
||||||
|
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
||||||
|
return getAllAvailableSubtypes().filter { it.isAsciiCapable && it.mainLayoutName()?.startsWith(CUSTOM_LAYOUT_PREFIX) == false }
|
||||||
|
.mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
|
||||||
|
return getSubtypesForLocale(locale).mapNotNullTo(HashSet()) { it.mainLayoutName() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getContent(layoutType: LayoutType, layoutName: String, context: Context): String {
|
||||||
|
val layouts = context.assets.list(layoutType.folder)!!
|
||||||
|
layouts.firstOrNull { it.startsWith("$layoutName.") }
|
||||||
|
?.let { return context.assets.open(layoutType.folder + it).reader().readText() }
|
||||||
|
val fallback = layouts.first { it.startsWith(layoutType.default) } // must exist!
|
||||||
|
return context.assets.open(layoutType.folder + fallback).reader().readText()
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
@ -26,6 +25,8 @@ import java.io.IOException
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.util.EnumMap
|
import java.util.EnumMap
|
||||||
|
|
||||||
|
// todo: object like LayoutUtils
|
||||||
|
|
||||||
fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) {
|
fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) {
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
|
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
|
||||||
|
@ -175,14 +176,23 @@ fun onCustomLayoutFileListChanged() {
|
||||||
|
|
||||||
private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
|
private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
|
||||||
|
|
||||||
// undo the name changes in loadCustomLayout when clicking ok
|
fun getCustomLayoutDisplayName(layoutName: String) =
|
||||||
fun getLayoutDisplayName(layoutName: String) =
|
|
||||||
try {
|
try {
|
||||||
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringAfter(".").substringBeforeLast("."))
|
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringBeforeLast("."))
|
||||||
} catch (_: NumberFormatException) {
|
} catch (_: NumberFormatException) {
|
||||||
layoutName
|
layoutName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCustomLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
|
||||||
|
|
||||||
|
fun isCustomLayout(layoutName: String) = layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)
|
||||||
|
|
||||||
|
fun getCustomLayoutFile(layoutName: String, layoutType: LayoutType, context: Context): File {
|
||||||
|
val file = File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder + layoutName)
|
||||||
|
file.parentFile?.mkdirs()
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
||||||
getCustomLayoutFile(layoutName, context).delete()
|
getCustomLayoutFile(layoutName, context).delete()
|
||||||
}
|
}
|
||||||
|
@ -193,7 +203,7 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String?
|
||||||
setText(startContent ?: file.readText())
|
setText(startContent ?: file.readText())
|
||||||
}
|
}
|
||||||
val builder = AlertDialog.Builder(context)
|
val builder = AlertDialog.Builder(context)
|
||||||
.setTitle(getLayoutDisplayName(layoutName))
|
.setTitle(getCustomLayoutDisplayName(layoutName))
|
||||||
.setView(editText)
|
.setView(editText)
|
||||||
.setPositiveButton(R.string.save) { _, _ ->
|
.setPositiveButton(R.string.save) { _, _ ->
|
||||||
val content = editText.text.toString()
|
val content = editText.text.toString()
|
||||||
|
|
|
@ -266,8 +266,8 @@ public final class SubtypeLocaleUtils {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) {
|
public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) {
|
||||||
if (layoutName.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX))
|
if (LayoutUtilsCustomKt.isCustomLayout(layoutName))
|
||||||
return CustomLayoutUtilsKt.getLayoutDisplayName(layoutName);
|
return LayoutUtilsCustomKt.getCustomLayoutDisplayName(layoutName);
|
||||||
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
// todo: move some parts, to subtypeUtils, and only keep actual settings?
|
||||||
|
|
||||||
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided,
|
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided,
|
||||||
* subtypes for system locales will be returned, or en-US if none found. */
|
* subtypes for system locales will be returned, or en-US if none found. */
|
||||||
fun getEnabledSubtypes(prefs: SharedPreferences, fallback: Boolean = false): List<InputMethodSubtype> {
|
fun getEnabledSubtypes(prefs: SharedPreferences, fallback: Boolean = false): List<InputMethodSubtype> {
|
||||||
|
@ -148,6 +150,8 @@ fun hasMatchingSubtypeForLocale(locale: Locale): Boolean {
|
||||||
return !resourceSubtypesByLocale[locale].isNullOrEmpty()
|
return !resourceSubtypesByLocale[locale].isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSubtypesForLocale(locale: Locale): List<InputMethodSubtype> = resourceSubtypesByLocale[locale].orEmpty()
|
||||||
|
|
||||||
fun getAvailableSubtypeLocales(): Collection<Locale> {
|
fun getAvailableSubtypeLocales(): Collection<Locale> {
|
||||||
require(initialized)
|
require(initialized)
|
||||||
return resourceSubtypesByLocale.keys
|
return resourceSubtypesByLocale.keys
|
||||||
|
|
|
@ -26,6 +26,6 @@ fun InputMethodSubtype.mainLayoutName(): String? {
|
||||||
fun InputMethodSubtype.displayName(context: Context): CharSequence {
|
fun InputMethodSubtype.displayName(context: Context): CharSequence {
|
||||||
val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this)
|
val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this)
|
||||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX))
|
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX))
|
||||||
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${getLayoutDisplayName(layoutName)})"
|
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${getCustomLayoutDisplayName(layoutName)})"
|
||||||
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import helium314.keyboard.settings.screens.createAdvancedSettings
|
||||||
import helium314.keyboard.settings.screens.createAppearanceSettings
|
import helium314.keyboard.settings.screens.createAppearanceSettings
|
||||||
import helium314.keyboard.settings.screens.createCorrectionSettings
|
import helium314.keyboard.settings.screens.createCorrectionSettings
|
||||||
import helium314.keyboard.settings.screens.createGestureTypingSettings
|
import helium314.keyboard.settings.screens.createGestureTypingSettings
|
||||||
|
import helium314.keyboard.settings.screens.createLayoutSettings
|
||||||
import helium314.keyboard.settings.screens.createPreferencesSettings
|
import helium314.keyboard.settings.screens.createPreferencesSettings
|
||||||
import helium314.keyboard.settings.screens.createToolbarSettings
|
import helium314.keyboard.settings.screens.createToolbarSettings
|
||||||
|
|
||||||
|
@ -63,7 +64,8 @@ class Setting(
|
||||||
// intentionally not putting individual debug settings in here so user knows the context
|
// intentionally not putting individual debug settings in here so user knows the context
|
||||||
private fun createSettings(context: Context) = createAboutSettings(context) + createAppearanceSettings(context) +
|
private fun createSettings(context: Context) = createAboutSettings(context) + createAppearanceSettings(context) +
|
||||||
createCorrectionSettings(context) + createPreferencesSettings(context) + createToolbarSettings(context) +
|
createCorrectionSettings(context) + createPreferencesSettings(context) + createToolbarSettings(context) +
|
||||||
createAdvancedSettings(context) + if (JniUtils.sHaveGestureLib) createGestureTypingSettings(context) else emptyList()
|
createLayoutSettings(context) + createAdvancedSettings(context) +
|
||||||
|
if (JniUtils.sHaveGestureLib) createGestureTypingSettings(context) else emptyList()
|
||||||
|
|
||||||
object SettingsWithoutKey {
|
object SettingsWithoutKey {
|
||||||
const val EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary"
|
const val EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import helium314.keyboard.settings.screens.AppearanceScreen
|
||||||
import helium314.keyboard.settings.screens.ColorsScreen
|
import helium314.keyboard.settings.screens.ColorsScreen
|
||||||
import helium314.keyboard.settings.screens.DebugScreen
|
import helium314.keyboard.settings.screens.DebugScreen
|
||||||
import helium314.keyboard.settings.screens.GestureTypingScreen
|
import helium314.keyboard.settings.screens.GestureTypingScreen
|
||||||
|
import helium314.keyboard.settings.screens.LayoutScreen
|
||||||
import helium314.keyboard.settings.screens.MainSettingsScreen
|
import helium314.keyboard.settings.screens.MainSettingsScreen
|
||||||
import helium314.keyboard.settings.screens.PreferencesScreen
|
import helium314.keyboard.settings.screens.PreferencesScreen
|
||||||
import helium314.keyboard.settings.screens.TextCorrectionScreen
|
import helium314.keyboard.settings.screens.TextCorrectionScreen
|
||||||
|
@ -57,6 +58,7 @@ fun SettingsNavHost(
|
||||||
onClickAdvanced = { navController.navigate(SettingsDestination.Advanced) },
|
onClickAdvanced = { navController.navigate(SettingsDestination.Advanced) },
|
||||||
onClickAppearance = { navController.navigate(SettingsDestination.Appearance) },
|
onClickAppearance = { navController.navigate(SettingsDestination.Appearance) },
|
||||||
onClickLanguage = { navController.navigate(SettingsDestination.Languages) },
|
onClickLanguage = { navController.navigate(SettingsDestination.Languages) },
|
||||||
|
onClickLayouts = { navController.navigate(SettingsDestination.Layouts) },
|
||||||
onClickBack = ::goBack,
|
onClickBack = ::goBack,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -92,6 +94,9 @@ fun SettingsNavHost(
|
||||||
composable(SettingsDestination.Languages) {
|
composable(SettingsDestination.Languages) {
|
||||||
// LanguageScreen(onClickBack = ::goBack)
|
// LanguageScreen(onClickBack = ::goBack)
|
||||||
}
|
}
|
||||||
|
composable(SettingsDestination.Layouts) {
|
||||||
|
LayoutScreen(onClickBack = ::goBack)
|
||||||
|
}
|
||||||
composable(SettingsDestination.Colors) {
|
composable(SettingsDestination.Colors) {
|
||||||
ColorsScreen(isNight = false, onClickBack = ::goBack)
|
ColorsScreen(isNight = false, onClickBack = ::goBack)
|
||||||
}
|
}
|
||||||
|
@ -117,6 +122,7 @@ object SettingsDestination {
|
||||||
const val ColorsNight = "colors_night"
|
const val ColorsNight = "colors_night"
|
||||||
const val PersonalDictionary = "personal_dictionary"
|
const val PersonalDictionary = "personal_dictionary"
|
||||||
const val Languages = "languages"
|
const val Languages = "languages"
|
||||||
|
const val Layouts = "layouts"
|
||||||
val navTarget = MutableStateFlow(Settings)
|
val navTarget = MutableStateFlow(Settings)
|
||||||
|
|
||||||
private val navScope = CoroutineScope(Dispatchers.Default)
|
private val navScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
|
@ -181,7 +181,7 @@ private fun AddColorRow(onDismissRequest: () -> Unit, userColors: Collection<Str
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColorItemRow(onDismissRequest: () -> Unit, item: String, isSelected: Boolean, isUser: Boolean, targetScreen: String, prefKey: String) {
|
private fun ColorItemRow(onDismissRequest: () -> Unit, item: String, isSelected: Boolean, isUser: Boolean, targetScreen: String, prefKey: String) {
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
val prefs = ctx.prefs()
|
val prefs = ctx.prefs()
|
||||||
Row(
|
Row(
|
||||||
|
@ -217,6 +217,7 @@ fun ColorItemRow(onDismissRequest: () -> Unit, item: String, isSelected: Boolean
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
|
// todo: maybe no need to set it as default when using the navigation specials
|
||||||
prefs.edit().putString(prefKey, item).apply()
|
prefs.edit().putString(prefKey, item).apply()
|
||||||
SettingsDestination.navigateTo(targetScreen)
|
SettingsDestination.navigateTo(targetScreen)
|
||||||
keyboardNeedsReload = true
|
keyboardNeedsReload = true
|
||||||
|
@ -272,7 +273,7 @@ private fun loadColorString(colorString: String, prefs: SharedPreferences): Bool
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewListPickerDialog() {
|
private fun Preview() {
|
||||||
ColorThemePickerDialog(
|
ColorThemePickerDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
setting = Setting(LocalContext.current, "", R.string.settings) {},
|
setting = Setting(LocalContext.current, "", R.string.settings) {},
|
||||||
|
|
|
@ -3,7 +3,9 @@ package helium314.keyboard.settings.dialogs
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -12,50 +14,78 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
import helium314.keyboard.latin.utils.Log
|
import helium314.keyboard.latin.utils.Log
|
||||||
import helium314.keyboard.latin.utils.checkLayout
|
import helium314.keyboard.latin.utils.checkLayout
|
||||||
import helium314.keyboard.latin.utils.getCustomLayoutFile
|
import helium314.keyboard.latin.utils.getCustomLayoutFile
|
||||||
import helium314.keyboard.latin.utils.getLayoutDisplayName
|
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
|
||||||
|
import helium314.keyboard.latin.utils.getCustomLayoutName
|
||||||
|
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||||
|
import helium314.keyboard.latin.utils.isCustomLayout
|
||||||
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
|
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
|
||||||
import helium314.keyboard.settings.keyboardNeedsReload
|
import helium314.keyboard.settings.keyboardNeedsReload
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// todo: make it wider!
|
||||||
|
// maybe make it a completely separate dialog, not even using the 3-button thing?
|
||||||
|
// though we could provide with parameter, and maybe some sort of reduce-padding option
|
||||||
@Composable
|
@Composable
|
||||||
fun LayoutEditDialog(
|
fun LayoutEditDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
layoutName: String,
|
layoutType: LayoutType,
|
||||||
|
initialLayoutName: String,
|
||||||
startContent: String? = null,
|
startContent: String? = null,
|
||||||
displayName: String? = null
|
isNameValid: (String) -> Boolean
|
||||||
) {
|
) {
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
val file = getCustomLayoutFile(layoutName, ctx)
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
var showDeleteConfirmation by rememberSaveable { mutableStateOf(false) }
|
val startIsCustom = isCustomLayout(initialLayoutName)
|
||||||
|
var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||||
|
mutableStateOf(TextFieldValue(
|
||||||
|
if (startIsCustom) getCustomLayoutDisplayName(initialLayoutName)
|
||||||
|
else initialLayoutName.getStringResourceOrName("layout_", ctx)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
val nameValid = displayNameValue.text.isNotBlank()
|
||||||
|
&& (
|
||||||
|
(startIsCustom && getCustomLayoutName(displayNameValue.text) == initialLayoutName)
|
||||||
|
|| isNameValid(getCustomLayoutName(displayNameValue.text))
|
||||||
|
)
|
||||||
|
|
||||||
TextInputDialog(
|
TextInputDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = {
|
||||||
|
job?.cancel()
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
onConfirmed = {
|
onConfirmed = {
|
||||||
file.parentFile?.mkdir()
|
val newLayoutName = getCustomLayoutName(displayNameValue.text)
|
||||||
file.writeText(it)
|
if (startIsCustom && initialLayoutName != newLayoutName)
|
||||||
|
getCustomLayoutFile(initialLayoutName, layoutType, ctx).delete()
|
||||||
|
getCustomLayoutFile(newLayoutName, layoutType, ctx).writeText(it)
|
||||||
onCustomLayoutFileListChanged()
|
onCustomLayoutFileListChanged()
|
||||||
keyboardNeedsReload = true
|
keyboardNeedsReload = true
|
||||||
},
|
},
|
||||||
confirmButtonText = stringResource(R.string.save),
|
confirmButtonText = stringResource(R.string.save),
|
||||||
neutralButtonText = if (displayName != null && file.exists()) stringResource(R.string.delete) else null,
|
initialText = startContent ?: getCustomLayoutFile(initialLayoutName, layoutType, ctx).readText(),
|
||||||
onNeutral = {
|
|
||||||
if (!file.exists()) return@TextInputDialog
|
|
||||||
file.delete()
|
|
||||||
onCustomLayoutFileListChanged()
|
|
||||||
keyboardNeedsReload = true
|
|
||||||
},
|
|
||||||
initialText = startContent ?: file.readText(),
|
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
title = { Text(displayName ?: getLayoutDisplayName(layoutName)) },
|
title = {
|
||||||
|
TextField(
|
||||||
|
value = displayNameValue,
|
||||||
|
onValueChange = { displayNameValue = it },
|
||||||
|
isError = !nameValid,
|
||||||
|
supportingText = { if (!nameValid) Text(stringResource(R.string.name_invalid)) },
|
||||||
|
trailingIcon = { if (!nameValid) Icon(painterResource(R.drawable.ic_close), null) },
|
||||||
|
// textStyle = MaterialTheme.typography.titleMedium, // todo: only makes it a tiny bit smaller, find a better way
|
||||||
|
)
|
||||||
|
},
|
||||||
checkTextValid = {
|
checkTextValid = {
|
||||||
val valid = checkLayout(it, ctx)
|
val valid = checkLayout(it, ctx)
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
|
@ -68,23 +98,12 @@ fun LayoutEditDialog(
|
||||||
Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show()
|
Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
valid
|
valid && nameValid // don't allow saving with invalid name, but inform user about issues with layout content
|
||||||
},
|
},
|
||||||
modifier = Modifier.imePadding(),
|
modifier = Modifier.imePadding(),
|
||||||
// decorFitsSystemWindows = false is necessary so the dialog is not covered by keyboard
|
// decorFitsSystemWindows = false is necessary so the dialog is not covered by keyboard
|
||||||
// but this also stops the background from being darkened... great idea to combine both
|
// but this also stops the background from being darkened... great idea to combine both
|
||||||
|
// todo: also it results in an ugly effect when adding a new layout... need to find something else
|
||||||
properties = DialogProperties(decorFitsSystemWindows = false)
|
properties = DialogProperties(decorFitsSystemWindows = false)
|
||||||
)
|
)
|
||||||
if (showDeleteConfirmation)
|
|
||||||
ConfirmationDialog(
|
|
||||||
onDismissRequest = { showDeleteConfirmation = false },
|
|
||||||
onConfirmed = {
|
|
||||||
onDismissRequest()
|
|
||||||
file.delete()
|
|
||||||
onCustomLayoutFileListChanged()
|
|
||||||
keyboardNeedsReload = true
|
|
||||||
},
|
|
||||||
text = { Text(stringResource(R.string.delete_layout, displayName ?: "")) },
|
|
||||||
confirmButtonText = stringResource(R.string.delete)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
package helium314.keyboard.settings.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.settings.Defaults.default
|
||||||
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
|
import helium314.keyboard.latin.utils.LayoutUtils
|
||||||
|
import helium314.keyboard.latin.utils.Log
|
||||||
|
import helium314.keyboard.latin.utils.checkLayout
|
||||||
|
import helium314.keyboard.latin.utils.getActivity
|
||||||
|
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
|
||||||
|
import helium314.keyboard.latin.utils.getCustomLayoutFiles
|
||||||
|
import helium314.keyboard.latin.utils.getCustomLayoutName
|
||||||
|
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||||
|
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
|
||||||
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
import helium314.keyboard.settings.Setting
|
||||||
|
import helium314.keyboard.settings.SettingsActivity
|
||||||
|
import helium314.keyboard.settings.keyboardNeedsReload
|
||||||
|
|
||||||
|
// modified copy of ColorPickerDialog, later check whether stuff can be re-used
|
||||||
|
@Composable
|
||||||
|
fun LayoutPickerDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
setting: Setting,
|
||||||
|
layoutType: LayoutType,
|
||||||
|
) {
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
val prefs = ctx.prefs()
|
||||||
|
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
|
||||||
|
if ((b?.value ?: 0) < 0)
|
||||||
|
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
|
||||||
|
|
||||||
|
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
|
||||||
|
val internalLayouts = LayoutUtils.getAvailableLayouts(layoutType, ctx)
|
||||||
|
// todo: getCustomLayoutFiles does not work nicely for main layout, but currently this dialog is not used for them
|
||||||
|
val customLayouts = getCustomLayoutFiles(layoutType, ctx).map { it.name }.sorted()
|
||||||
|
val layouts = internalLayouts + customLayouts + ""
|
||||||
|
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LaunchedEffect(currentLayout) {
|
||||||
|
val index = layouts.indexOfFirst { it == currentLayout }
|
||||||
|
if (index != -1) state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3)
|
||||||
|
}
|
||||||
|
var errorDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var newLayoutDialog: Pair<String, String?>? by rememberSaveable { mutableStateOf(null) }
|
||||||
|
val loadFilePicker = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult
|
||||||
|
val uri = it.data?.data ?: return@rememberLauncherForActivityResult
|
||||||
|
val cr = ctx.getActivity()?.contentResolver ?: return@rememberLauncherForActivityResult
|
||||||
|
val name = cr.query(uri, null, null, null, null)?.use { c ->
|
||||||
|
if (!c.moveToFirst()) return@use null
|
||||||
|
val index = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (index < 0) null
|
||||||
|
else c.getString(index)
|
||||||
|
}
|
||||||
|
cr.openInputStream(uri)?.use {
|
||||||
|
val content = it.reader().readText()
|
||||||
|
errorDialog = !checkLayout(content, ctx)
|
||||||
|
if (!errorDialog)
|
||||||
|
newLayoutDialog = (name ?: layoutType.default) to content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThreeButtonAlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
cancelButtonText = stringResource(R.string.dialog_close),
|
||||||
|
onConfirmed = { },
|
||||||
|
confirmButtonText = null,
|
||||||
|
neutralButtonText = stringResource(R.string.button_load_custom),
|
||||||
|
onNeutral = {
|
||||||
|
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) },
|
||||||
|
title = { Text(setting.title) },
|
||||||
|
text = {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides MaterialTheme.typography.bodyLarge
|
||||||
|
) {
|
||||||
|
LazyColumn(state = state) {
|
||||||
|
items(layouts) { item ->
|
||||||
|
if (item == "") {
|
||||||
|
AddLayoutRow({ newLayoutDialog = it to "" }, customLayouts)
|
||||||
|
} else {
|
||||||
|
LayoutItemRow(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onClickEdit = { newLayoutDialog = it },
|
||||||
|
onDelete = { deletedLayout ->
|
||||||
|
if (item == deletedLayout) {
|
||||||
|
prefs.edit().remove(Settings.PREF_LAYOUT_PREFIX + layoutType.name).apply()
|
||||||
|
keyboardNeedsReload = true
|
||||||
|
}
|
||||||
|
getCustomLayoutFiles(layoutType, ctx).firstOrNull { it.name == deletedLayout }?.delete()
|
||||||
|
onCustomLayoutFileListChanged()
|
||||||
|
},
|
||||||
|
layoutType = layoutType,
|
||||||
|
layoutName = item,
|
||||||
|
isSelected = item == currentLayout,
|
||||||
|
isCustom = item in customLayouts
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (errorDialog)
|
||||||
|
InfoDialog(stringResource(R.string.file_read_error)) { errorDialog = false }
|
||||||
|
if (newLayoutDialog != null) {
|
||||||
|
LayoutEditDialog(
|
||||||
|
onDismissRequest = { newLayoutDialog = null },
|
||||||
|
layoutType = layoutType,
|
||||||
|
initialLayoutName = newLayoutDialog?.first ?: layoutType.default,
|
||||||
|
startContent = newLayoutDialog?.second,
|
||||||
|
isNameValid = { it.isNotBlank() && it !in customLayouts }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AddLayoutRow(onNewLayout: (String) -> Unit, userLayouts: Collection<String>) {
|
||||||
|
var textValue by remember { mutableStateOf(TextFieldValue()) }
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(start = 10.dp)
|
||||||
|
) {
|
||||||
|
Icon(painterResource(R.drawable.ic_plus), stringResource(R.string.add))
|
||||||
|
TextField(
|
||||||
|
value = textValue,
|
||||||
|
onValueChange = { textValue = it },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
enabled = textValue.text.isNotEmpty() && getCustomLayoutName(textValue.text) !in userLayouts,
|
||||||
|
onClick = { onNewLayout(textValue.text) }
|
||||||
|
) { Icon(painterResource(R.drawable.ic_edit), null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LayoutItemRow(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onClickEdit: (Pair<String, String?>) -> Unit,
|
||||||
|
onDelete: (String) -> Unit,
|
||||||
|
layoutType: LayoutType,
|
||||||
|
layoutName: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
isCustom: Boolean,
|
||||||
|
) {
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
val prefs = ctx.prefs()
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
onDismissRequest()
|
||||||
|
Settings.writeDefaultLayoutName(layoutName, layoutType, prefs)
|
||||||
|
keyboardNeedsReload = true
|
||||||
|
}
|
||||||
|
.padding(start = 6.dp)
|
||||||
|
.heightIn(min = 40.dp)
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
Settings.writeDefaultLayoutName(layoutName, layoutType, prefs)
|
||||||
|
keyboardNeedsReload = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (isCustom) getCustomLayoutDisplayName(layoutName)
|
||||||
|
else layoutName.getStringResourceOrName("layout_", ctx),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
if (isCustom) {
|
||||||
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
IconButton(
|
||||||
|
onClick = { showDeleteDialog = true }
|
||||||
|
) { Icon(painterResource(R.drawable.ic_bin), null) }
|
||||||
|
if (showDeleteDialog)
|
||||||
|
ConfirmationDialog(
|
||||||
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
|
text = { Text(stringResource(R.string.delete_layout, getCustomLayoutDisplayName(layoutName))) },
|
||||||
|
confirmButtonText = stringResource(R.string.delete),
|
||||||
|
onConfirmed = {
|
||||||
|
showDeleteDialog = false
|
||||||
|
onDelete(layoutName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = { onClickEdit(layoutName to (if (isCustom) null else LayoutUtils.getContent(layoutType, layoutName, ctx))) }
|
||||||
|
) { Icon(painterResource(R.drawable.ic_edit), null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
LayoutPickerDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
setting = Setting(LocalContext.current, "", R.string.settings) {},
|
||||||
|
layoutType = LayoutType.SYMBOLS
|
||||||
|
)
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
@ -48,7 +49,7 @@ fun ThreeButtonAlertDialog(
|
||||||
properties = properties
|
properties = properties
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.sizeIn(minWidth = 280.dp, maxWidth = 560.dp),
|
modifier = modifier.widthIn(min = 280.dp, max = 560.dp),
|
||||||
propagateMinConstraints = true
|
propagateMinConstraints = true
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
|
|
|
@ -14,7 +14,7 @@ import helium314.keyboard.settings.Setting
|
||||||
import helium314.keyboard.settings.dialogs.LayoutEditDialog
|
import helium314.keyboard.settings.dialogs.LayoutEditDialog
|
||||||
import helium314.keyboard.settings.dialogs.ListPickerDialog
|
import helium314.keyboard.settings.dialogs.ListPickerDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
/*
|
||||||
@Composable
|
@Composable
|
||||||
fun LayoutEditPreference(
|
fun LayoutEditPreference(
|
||||||
setting: Setting,
|
setting: Setting,
|
||||||
|
@ -57,3 +57,4 @@ fun LayoutEditPreference(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
|
@ -27,11 +27,6 @@ import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
import helium314.keyboard.latin.settings.DebugSettings
|
import helium314.keyboard.latin.settings.DebugSettings
|
||||||
import helium314.keyboard.latin.settings.Defaults
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_NORMAL
|
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS
|
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED
|
|
||||||
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
|
||||||
import helium314.keyboard.latin.utils.getStringResourceOrName
|
|
||||||
import helium314.keyboard.latin.utils.prefs
|
import helium314.keyboard.latin.utils.prefs
|
||||||
import helium314.keyboard.settings.SettingsContainer
|
import helium314.keyboard.settings.SettingsContainer
|
||||||
import helium314.keyboard.settings.preferences.ListPreference
|
import helium314.keyboard.settings.preferences.ListPreference
|
||||||
|
@ -47,7 +42,6 @@ import helium314.keyboard.settings.Theme
|
||||||
import helium314.keyboard.settings.dialogs.TextInputDialog
|
import helium314.keyboard.settings.dialogs.TextInputDialog
|
||||||
import helium314.keyboard.settings.keyboardNeedsReload
|
import helium314.keyboard.settings.keyboardNeedsReload
|
||||||
import helium314.keyboard.settings.preferences.BackupRestorePreference
|
import helium314.keyboard.settings.preferences.BackupRestorePreference
|
||||||
import helium314.keyboard.settings.preferences.LayoutEditPreference
|
|
||||||
import helium314.keyboard.settings.preferences.LoadGestureLibPreference
|
import helium314.keyboard.settings.preferences.LoadGestureLibPreference
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -192,23 +186,6 @@ fun createAdvancedSettings(context: Context) = listOf(
|
||||||
)
|
)
|
||||||
ListPreference(it, items, Defaults.PREF_MORE_POPUP_KEYS) { KeyboardLayoutSet.onSystemLocaleChanged() }
|
ListPreference(it, items, Defaults.PREF_MORE_POPUP_KEYS) { KeyboardLayoutSet.onSystemLocaleChanged() }
|
||||||
},
|
},
|
||||||
/* Setting(context, SettingsWithoutKey.CUSTOM_SYMBOLS_NUMBER_LAYOUTS, R.string.customize_symbols_number_layouts) { setting ->
|
|
||||||
LayoutEditPreference(
|
|
||||||
setting = setting,
|
|
||||||
items = RawKeyboardParser.symbolAndNumberLayouts,
|
|
||||||
getItemName = { it.getStringResourceOrName("layout_", LocalContext.current) },
|
|
||||||
getDefaultLayout = { LocalContext.current.assets.list("layouts")?.firstOrNull { it.startsWith("$it.") } }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Setting(context, SettingsWithoutKey.CUSTOM_FUNCTIONAL_LAYOUTS, R.string.customize_functional_key_layouts) { setting ->
|
|
||||||
LayoutEditPreference(
|
|
||||||
setting = setting,
|
|
||||||
items = listOf(CUSTOM_FUNCTIONAL_LAYOUT_NORMAL, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED)
|
|
||||||
.map { it.substringBeforeLast(".") },
|
|
||||||
getItemName = { it.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", LocalContext.current) },
|
|
||||||
getDefaultLayout = { if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json" }
|
|
||||||
)
|
|
||||||
},*/ // todo: these settings are disabled for now -> remove them and use a layoutScreen instead
|
|
||||||
Setting(context, SettingsWithoutKey.BACKUP_RESTORE, R.string.backup_restore_title) {
|
Setting(context, SettingsWithoutKey.BACKUP_RESTORE, R.string.backup_restore_title) {
|
||||||
BackupRestorePreference(it)
|
BackupRestorePreference(it)
|
||||||
},
|
},
|
||||||
|
|
|
@ -136,7 +136,7 @@ fun ColorsScreen(
|
||||||
nameField = it
|
nameField = it
|
||||||
},
|
},
|
||||||
isError = !nameValid,
|
isError = !nameValid,
|
||||||
// supportingText = { if (!nameValid) Text("name already in use") } // this is cutting off bottom half of the actual text...
|
// supportingText = { if (!nameValid) Text(stringResource(R.string.name_invalid) } // todo: this is cutting off bottom half of the actual text...
|
||||||
trailingIcon = { if (!nameValid) Icon(painterResource(R.drawable.ic_close), null) }
|
trailingIcon = { if (!nameValid) Icon(painterResource(R.drawable.ic_close), null) }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package helium314.keyboard.settings.screens
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
|
import helium314.keyboard.latin.utils.LayoutType.Companion.displayNameId
|
||||||
|
import helium314.keyboard.latin.utils.Log
|
||||||
|
import helium314.keyboard.latin.utils.getActivity
|
||||||
|
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
|
||||||
|
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||||
|
import helium314.keyboard.latin.utils.isCustomLayout
|
||||||
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
import helium314.keyboard.settings.SearchSettingsScreen
|
||||||
|
import helium314.keyboard.settings.Setting
|
||||||
|
import helium314.keyboard.settings.SettingsActivity
|
||||||
|
import helium314.keyboard.settings.dialogs.LayoutPickerDialog
|
||||||
|
import helium314.keyboard.settings.preferences.Preference
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LayoutScreen(
|
||||||
|
onClickBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
// todo: enable main layouts
|
||||||
|
// which layouts to show? all is too much, maybe limit to latin and layouts for enabled locales (and system locales?)
|
||||||
|
SearchSettingsScreen(
|
||||||
|
onClickBack = onClickBack,
|
||||||
|
title = stringResource(R.string.keyboard_layout_set),
|
||||||
|
settings = LayoutType.entries.filter { it != LayoutType.MAIN }.map { Settings.PREF_LAYOUT_PREFIX + it.name }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createLayoutSettings(context: Context) = listOf(
|
||||||
|
Setting(context, Settings.PREF_LAYOUT_PREFIX + LayoutType.MAIN, R.string.customize_functional_key_layouts) { // todo: title
|
||||||
|
// todo: actual content
|
||||||
|
},
|
||||||
|
) + LayoutType.entries.filter { it != LayoutType.MAIN }.map { layoutType ->
|
||||||
|
Setting(context, Settings.PREF_LAYOUT_PREFIX + layoutType, layoutType.displayNameId) { setting ->
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
val prefs = ctx.prefs()
|
||||||
|
val b = (ctx.getActivity() as? SettingsActivity)?.prefChanged?.collectAsState()
|
||||||
|
if ((b?.value ?: 0) < 0)
|
||||||
|
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
|
||||||
|
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
|
||||||
|
val displayName = if (isCustomLayout(currentLayout)) getCustomLayoutDisplayName(currentLayout)
|
||||||
|
else currentLayout.getStringResourceOrName("layout_", ctx)
|
||||||
|
Preference(
|
||||||
|
name = setting.title,
|
||||||
|
description = displayName,
|
||||||
|
onClick = { showDialog = true }
|
||||||
|
)
|
||||||
|
if (showDialog)
|
||||||
|
LayoutPickerDialog(
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
setting = setting,
|
||||||
|
layoutType = layoutType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ fun MainSettingsScreen(
|
||||||
onClickAdvanced: () -> Unit,
|
onClickAdvanced: () -> Unit,
|
||||||
onClickAppearance: () -> Unit,
|
onClickAppearance: () -> Unit,
|
||||||
onClickLanguage: () -> Unit,
|
onClickLanguage: () -> Unit,
|
||||||
|
onClickLayouts: () -> Unit,
|
||||||
onClickBack: () -> Unit,
|
onClickBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
|
@ -116,6 +117,17 @@ fun MainSettingsScreen(
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Preference(
|
||||||
|
name = stringResource(R.string.keyboard_layout_set),
|
||||||
|
onClick = onClickLayouts,
|
||||||
|
icon = R.drawable.ic_ime_switcher
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_arrow_left),
|
||||||
|
modifier = Modifier.scale(-1f, 1f),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
Preference(
|
Preference(
|
||||||
name = stringResource(R.string.settings_screen_advanced),
|
name = stringResource(R.string.settings_screen_advanced),
|
||||||
onClick = onClickAdvanced,
|
onClick = onClickAdvanced,
|
||||||
|
@ -181,7 +193,7 @@ fun MainSettingsScreen(
|
||||||
private fun PreviewScreen() {
|
private fun PreviewScreen() {
|
||||||
Theme(true) {
|
Theme(true) {
|
||||||
Surface {
|
Surface {
|
||||||
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {}, {})
|
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -987,6 +987,8 @@ New dictionary:
|
||||||
<string name="customize_icons">Customize icons</string>
|
<string name="customize_icons">Customize icons</string>
|
||||||
<!-- Confirmation message when resetting all custom icons -->
|
<!-- Confirmation message when resetting all custom icons -->
|
||||||
<string name="customize_icons_reset_message">Really reset all customized icons?</string>
|
<string name="customize_icons_reset_message">Really reset all customized icons?</string>
|
||||||
<!-- Confirmation message when deleting a something (used for custom colors) -->
|
<!-- Confirmation message when deleting a something (currently used for custom colors) -->
|
||||||
<string name="delete_confirmation">Really delete %s?</string>
|
<string name="delete_confirmation">Really delete %s?</string>
|
||||||
|
<!-- Message when chosen name is invalid (empty or already taked) -->
|
||||||
|
<string name="name_invalid">Invalid name</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Reference in a new issue