add layout screen for choosing and editing default (non-main) layouts

This commit is contained in:
Helium314 2025-02-15 10:52:56 +01:00
parent 6b86ea236b
commit a3e85bc664
24 changed files with 478 additions and 82 deletions

View file

@ -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 "//"

View file

@ -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

View file

@ -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()
} }

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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
}
} }
} }

View file

@ -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()
}
}

View file

@ -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()

View file

@ -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);
} }

View file

@ -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

View file

@ -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)
} }

View file

@ -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"

View file

@ -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)

View file

@ -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) {},

View file

@ -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)
)
} }

View file

@ -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
)
}

View file

@ -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(

View file

@ -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(
) )
} }
} }
*/

View file

@ -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)
}, },

View file

@ -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) }
) )
}, },

View file

@ -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
)
}
}

View file

@ -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({}, {}, {}, {}, {}, {}, {}, {}, {}, {})
} }
} }
} }

View file

@ -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>