mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-14 14:02:44 +00:00
add layout customizer dialog
This commit is contained in:
parent
6e3bedaf5c
commit
fff66913b8
6 changed files with 170 additions and 28 deletions
|
@ -81,6 +81,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
// search only in current pref screen, except when in main?
|
||||
// try getting rid of appcompat stuff (activity, dialogs, ...)
|
||||
// rearrange settings screens? now it should be very simple to do (definitely separate PR)
|
||||
// actually lenient json parsing is not good in a certain way: we should show an error if a json property is unknown
|
||||
// syntax highlighting for json? should show basic json errors
|
||||
|
||||
// preliminary results:
|
||||
// looks ok (ugly M3 switches)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package helium314.keyboard.settings.dialogs
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.utils.Log
|
||||
import helium314.keyboard.latin.utils.checkLayout
|
||||
import helium314.keyboard.latin.utils.getCustomLayoutFile
|
||||
import helium314.keyboard.latin.utils.getLayoutDisplayName
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun CustomizeLayoutDialog(
|
||||
layoutName: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
startContent: String? = null,
|
||||
displayName: String? = null,
|
||||
) {
|
||||
val ctx = LocalContext.current
|
||||
val file = getCustomLayoutFile(layoutName, ctx)
|
||||
val scope = rememberCoroutineScope()
|
||||
var job: Job? = null
|
||||
|
||||
TextInputDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirmed = { },
|
||||
initialText = startContent ?: file.readText(),
|
||||
title = { Text(displayName ?: getLayoutDisplayName(layoutName)) },
|
||||
checkTextValid = {
|
||||
val valid = checkLayout(it, ctx)
|
||||
job?.cancel()
|
||||
if (!valid) {
|
||||
job = scope.launch {
|
||||
delay(3000)
|
||||
val message = Log.getLog(10)
|
||||
.lastOrNull { it.tag == "CustomLayoutUtils" }?.message
|
||||
?.split("\n")?.take(2)?.joinToString("\n")
|
||||
Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
valid
|
||||
},
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ package helium314.keyboard.settings.dialogs
|
|||
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
|
||||
|
@ -35,6 +36,7 @@ fun <T: Any> ListPickerDialog(
|
|||
selectedItem: T? = null,
|
||||
getItemName: (@Composable (T) -> String) = { it.toString() },
|
||||
confirmImmediately: Boolean = true,
|
||||
showRadioButtons: Boolean = true,
|
||||
) {
|
||||
var selected by remember { mutableStateOf(selectedItem) }
|
||||
val state = rememberLazyListState()
|
||||
|
@ -68,12 +70,14 @@ fun <T: Any> ListPickerDialog(
|
|||
selected = item
|
||||
}
|
||||
.padding(horizontal = 24.dp)
|
||||
.heightIn(min = 40.dp)
|
||||
) {
|
||||
Text(
|
||||
text = getItemName(item),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (showRadioButtons)
|
||||
RadioButton(
|
||||
selected = selected == item,
|
||||
onClick = {
|
||||
|
|
|
@ -39,10 +39,9 @@ fun TextInputDialog(
|
|||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
var value by remember {
|
||||
mutableStateOf(TextFieldValue(initialText, selection = TextRange(initialText.length)))
|
||||
mutableStateOf(TextFieldValue(initialText, selection = TextRange(if (singleLine) initialText.length else 0)))
|
||||
}
|
||||
|
||||
// todo: this is not working any more?
|
||||
LaunchedEffect(initialText) { focusRequester.requestFocus() }
|
||||
|
||||
ThreeButtonAlertDialog(
|
||||
|
|
|
@ -3,14 +3,14 @@ package helium314.keyboard.settings.dialogs
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
||||
|
@ -45,7 +45,12 @@ fun ThreeButtonAlertDialog(
|
|||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
title = {
|
||||
// avoid way too large title (headlineSmall)
|
||||
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.titleLarge) {
|
||||
title?.invoke()
|
||||
}
|
||||
},
|
||||
text = text,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
|
|
|
@ -2,12 +2,15 @@ package helium314.keyboard.settings.screens
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
|
@ -15,11 +18,24 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.RawKeyboardParser
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.SystemBroadcastReceiver
|
||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||
import helium314.keyboard.latin.settings.DebugSettings
|
||||
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.Log
|
||||
import helium314.keyboard.latin.utils.checkLayout
|
||||
import helium314.keyboard.latin.utils.getCustomLayoutFile
|
||||
import helium314.keyboard.latin.utils.getCustomLayoutFiles
|
||||
import helium314.keyboard.latin.utils.getLayoutDisplayName
|
||||
import helium314.keyboard.latin.utils.getStringResourceOrName
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
import helium314.keyboard.settings.AllPrefs
|
||||
import helium314.keyboard.settings.ListPreference
|
||||
|
@ -33,8 +49,14 @@ import helium314.keyboard.settings.SettingsDestination
|
|||
import helium314.keyboard.settings.SliderPreference
|
||||
import helium314.keyboard.settings.SwitchPreference
|
||||
import helium314.keyboard.settings.Theme
|
||||
import helium314.keyboard.settings.dialogs.CustomizeLayoutDialog
|
||||
import helium314.keyboard.settings.dialogs.ListPickerDialog
|
||||
import helium314.keyboard.settings.dialogs.TextInputDialog
|
||||
import helium314.keyboard.settings.keyboardNeedsReload
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
fun AdvancedSettingsScreen(
|
||||
|
@ -159,13 +181,25 @@ fun createAdvancedPrefs(context: Context) = listOf(
|
|||
default = false
|
||||
)
|
||||
},
|
||||
PrefDef(context, Settings.PREF_CUSTOM_CURRENCY_KEY, R.string.customize_currencies) {
|
||||
PrefDef(context, Settings.PREF_CUSTOM_CURRENCY_KEY, R.string.customize_currencies) { def ->
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
name = it.title,
|
||||
name = def.title,
|
||||
onClick = { showDialog = true }
|
||||
)
|
||||
// if (showDialog) todo: show the currency customizer
|
||||
if (showDialog) {
|
||||
val prefs = LocalContext.current.prefs()
|
||||
TextInputDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
textInputLabel = { Text(stringResource(R.string.customize_currencies_detail)) },
|
||||
initialText = prefs.getString(def.key, "")!!,
|
||||
onConfirmed = { prefs.edit().putString(def.key, it).apply(); KeyboardLayoutSet.onSystemLocaleChanged() },
|
||||
title = { Text(stringResource(R.string.customize_currencies)) },
|
||||
neutralButtonText = if (prefs.contains(def.key)) stringResource(R.string.button_default) else null,
|
||||
onNeutral = { prefs.edit().remove(def.key).apply(); KeyboardLayoutSet.onSystemLocaleChanged() },
|
||||
checkTextValid = { it.splitOnWhitespace().none { it.length > 8 } }
|
||||
)
|
||||
}
|
||||
},
|
||||
PrefDef(context, Settings.PREF_MORE_POPUP_KEYS, R.string.show_popup_keys_title) { def ->
|
||||
val items = listOf(
|
||||
|
@ -174,29 +208,77 @@ fun createAdvancedPrefs(context: Context) = listOf(
|
|||
stringResource(R.string.show_popup_keys_more) to "more",
|
||||
stringResource(R.string.show_popup_keys_all) to "all",
|
||||
)
|
||||
ListPreference(def, items, "main")
|
||||
// todo: on value changed -> KeyboardLayoutSet.onSystemLocaleChanged()
|
||||
ListPreference(def, items, "main") { KeyboardLayoutSet.onSystemLocaleChanged() }
|
||||
},
|
||||
PrefDef(context, NonSettingsPrefs.CUSTOM_SYMBOLS_NUMBER_LAYOUTS, R.string.customize_symbols_number_layouts) {
|
||||
PrefDef(context, NonSettingsPrefs.CUSTOM_SYMBOLS_NUMBER_LAYOUTS, R.string.customize_symbols_number_layouts) { def ->
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val ctx = LocalContext.current
|
||||
var layout: String? by remember { mutableStateOf(null) }
|
||||
Preference(
|
||||
name = it.title,
|
||||
name = def.title,
|
||||
onClick = { showDialog = true }
|
||||
)
|
||||
if (showDialog) // todo: first the selection dialog, then the edit dialog
|
||||
TextInputDialog(
|
||||
if (showDialog) {
|
||||
ListPickerDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
onConfirmed = { }, // todo
|
||||
initialText = LocalContext.current.assets.open("layouts/dvorak.json").bufferedReader().readText()
|
||||
showRadioButtons = false,
|
||||
confirmImmediately = true,
|
||||
items = RawKeyboardParser.symbolAndNumberLayouts,
|
||||
getItemName = { it.getStringResourceOrName("layout_", ctx) },
|
||||
onItemSelected = { layout = it },
|
||||
title = { Text(def.title) }
|
||||
)
|
||||
}
|
||||
if (layout != null) {
|
||||
val customLayoutName = getCustomLayoutFiles(ctx).firstOrNull { it.name.startsWith("$CUSTOM_LAYOUT_PREFIX$layout.")}?.name
|
||||
val originalLayout = if (customLayoutName != null) null
|
||||
else {
|
||||
ctx.assets.list("layouts")?.firstOrNull { it.startsWith("$layout.") }
|
||||
?.let { ctx.assets.open("layouts" + File.separator + it).reader().readText() }
|
||||
}
|
||||
CustomizeLayoutDialog(
|
||||
layoutName = customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layout.",
|
||||
startContent = originalLayout,
|
||||
displayName = layout?.getStringResourceOrName("layout_", ctx),
|
||||
onDismissRequest = { layout = null}
|
||||
)
|
||||
}
|
||||
},
|
||||
PrefDef(context, NonSettingsPrefs.CUSTOM_FUNCTIONAL_LAYOUTS, R.string.customize_functional_key_layouts) {
|
||||
PrefDef(context, NonSettingsPrefs.CUSTOM_FUNCTIONAL_LAYOUTS, R.string.customize_functional_key_layouts) { def ->
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val ctx = LocalContext.current
|
||||
var layout: String? by remember { mutableStateOf(null) }
|
||||
Preference(
|
||||
name = it.title,
|
||||
name = def.title,
|
||||
onClick = { showDialog = true }
|
||||
)
|
||||
// if (showDialog) todo: show the customizer
|
||||
if (showDialog) {
|
||||
ListPickerDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
showRadioButtons = false,
|
||||
confirmImmediately = true,
|
||||
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_", ctx) },
|
||||
onItemSelected = { layout = it },
|
||||
title = { Text(def.title) }
|
||||
)
|
||||
}
|
||||
if (layout != null) {
|
||||
val customLayoutName = getCustomLayoutFiles(ctx).map { it.name }
|
||||
.firstOrNull { it.startsWith("$layout.") }
|
||||
val originalLayout = if (customLayoutName != null) null
|
||||
else {
|
||||
val defaultLayoutName = if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json"
|
||||
ctx.assets.open("layouts" + File.separator + defaultLayoutName).reader().readText()
|
||||
}
|
||||
CustomizeLayoutDialog(
|
||||
layoutName = customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layout.",
|
||||
startContent = originalLayout,
|
||||
displayName = layout?.substringAfter(CUSTOM_LAYOUT_PREFIX)?.getStringResourceOrName("layout_", ctx),
|
||||
onDismissRequest = { layout = null}
|
||||
)
|
||||
}
|
||||
},
|
||||
PrefDef(context, NonSettingsPrefs.BACKUP_RESTORE, R.string.backup_restore_title) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue