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?
|
// search only in current pref screen, except when in main?
|
||||||
// try getting rid of appcompat stuff (activity, dialogs, ...)
|
// try getting rid of appcompat stuff (activity, dialogs, ...)
|
||||||
// rearrange settings screens? now it should be very simple to do (definitely separate PR)
|
// 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:
|
// preliminary results:
|
||||||
// looks ok (ugly M3 switches)
|
// 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.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
@ -35,6 +36,7 @@ fun <T: Any> ListPickerDialog(
|
||||||
selectedItem: T? = null,
|
selectedItem: T? = null,
|
||||||
getItemName: (@Composable (T) -> String) = { it.toString() },
|
getItemName: (@Composable (T) -> String) = { it.toString() },
|
||||||
confirmImmediately: Boolean = true,
|
confirmImmediately: Boolean = true,
|
||||||
|
showRadioButtons: Boolean = true,
|
||||||
) {
|
) {
|
||||||
var selected by remember { mutableStateOf(selectedItem) }
|
var selected by remember { mutableStateOf(selectedItem) }
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
|
@ -68,22 +70,24 @@ fun <T: Any> ListPickerDialog(
|
||||||
selected = item
|
selected = item
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
|
.heightIn(min = 40.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = getItemName(item),
|
text = getItemName(item),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
RadioButton(
|
if (showRadioButtons)
|
||||||
selected = selected == item,
|
RadioButton(
|
||||||
onClick = {
|
selected = selected == item,
|
||||||
if (confirmImmediately) {
|
onClick = {
|
||||||
onDismissRequest()
|
if (confirmImmediately) {
|
||||||
onItemSelected(item)
|
onDismissRequest()
|
||||||
|
onItemSelected(item)
|
||||||
|
}
|
||||||
|
selected = item
|
||||||
}
|
}
|
||||||
selected = item
|
)
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,10 +39,9 @@ fun TextInputDialog(
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
var value by remember {
|
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() }
|
LaunchedEffect(initialText) { focusRequester.requestFocus() }
|
||||||
|
|
||||||
ThreeButtonAlertDialog(
|
ThreeButtonAlertDialog(
|
||||||
|
|
|
@ -3,14 +3,14 @@ package helium314.keyboard.settings.dialogs
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.contentColorFor
|
import androidx.compose.material3.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
||||||
|
@ -45,7 +45,12 @@ fun ThreeButtonAlertDialog(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = title,
|
title = {
|
||||||
|
// avoid way too large title (headlineSmall)
|
||||||
|
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.titleLarge) {
|
||||||
|
title?.invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
text = text,
|
text = text,
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
|
|
@ -2,12 +2,15 @@ package helium314.keyboard.settings.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
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
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
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.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.BuildConfig
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.SystemBroadcastReceiver
|
import helium314.keyboard.latin.SystemBroadcastReceiver
|
||||||
|
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
import helium314.keyboard.latin.settings.DebugSettings
|
import helium314.keyboard.latin.settings.DebugSettings
|
||||||
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.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.latin.utils.prefs
|
||||||
import helium314.keyboard.settings.AllPrefs
|
import helium314.keyboard.settings.AllPrefs
|
||||||
import helium314.keyboard.settings.ListPreference
|
import helium314.keyboard.settings.ListPreference
|
||||||
|
@ -33,8 +49,14 @@ import helium314.keyboard.settings.SettingsDestination
|
||||||
import helium314.keyboard.settings.SliderPreference
|
import helium314.keyboard.settings.SliderPreference
|
||||||
import helium314.keyboard.settings.SwitchPreference
|
import helium314.keyboard.settings.SwitchPreference
|
||||||
import helium314.keyboard.settings.Theme
|
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.dialogs.TextInputDialog
|
||||||
import helium314.keyboard.settings.keyboardNeedsReload
|
import helium314.keyboard.settings.keyboardNeedsReload
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AdvancedSettingsScreen(
|
fun AdvancedSettingsScreen(
|
||||||
|
@ -159,13 +181,25 @@ fun createAdvancedPrefs(context: Context) = listOf(
|
||||||
default = false
|
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) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
Preference(
|
Preference(
|
||||||
name = it.title,
|
name = def.title,
|
||||||
onClick = { showDialog = true }
|
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 ->
|
PrefDef(context, Settings.PREF_MORE_POPUP_KEYS, R.string.show_popup_keys_title) { def ->
|
||||||
val items = listOf(
|
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_more) to "more",
|
||||||
stringResource(R.string.show_popup_keys_all) to "all",
|
stringResource(R.string.show_popup_keys_all) to "all",
|
||||||
)
|
)
|
||||||
ListPreference(def, items, "main")
|
ListPreference(def, items, "main") { KeyboardLayoutSet.onSystemLocaleChanged() }
|
||||||
// todo: on value changed -> 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) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
var layout: String? by remember { mutableStateOf(null) }
|
||||||
Preference(
|
Preference(
|
||||||
name = it.title,
|
name = def.title,
|
||||||
onClick = { showDialog = true }
|
onClick = { showDialog = true }
|
||||||
)
|
)
|
||||||
if (showDialog) // todo: first the selection dialog, then the edit dialog
|
if (showDialog) {
|
||||||
TextInputDialog(
|
ListPickerDialog(
|
||||||
onDismissRequest = { showDialog = false },
|
onDismissRequest = { showDialog = false },
|
||||||
onConfirmed = { }, // todo
|
showRadioButtons = false,
|
||||||
initialText = LocalContext.current.assets.open("layouts/dvorak.json").bufferedReader().readText()
|
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) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
var layout: String? by remember { mutableStateOf(null) }
|
||||||
Preference(
|
Preference(
|
||||||
name = it.title,
|
name = def.title,
|
||||||
onClick = { showDialog = true }
|
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) {
|
PrefDef(context, NonSettingsPrefs.BACKUP_RESTORE, R.string.backup_restore_title) {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue