Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit d8e78dd3 authored by Wolf-Martell Montwé's avatar Wolf-Martell Montwé Committed by GitHub
Browse files

Merge pull request #9152 from wmontwe/change-text-preference-dialog-to-auto-focus

Change text preference dialog to auto focus
parents 255e128b d851554c
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
package app.k9mail.core.ui.compose.designsystem.molecule.input

import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
        )
    }
}

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputIsRequiredPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
            label = "Text input is required",
            isRequired = true,
        )
    }
}

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithErrorPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
            errorMessage = "Text input error",
        )
    }
}

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithAnnotatedStringPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
            text = TextFieldValue(
                annotatedString = buildAnnotatedString {
                    append("Text input with ")
                    withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
                        append("Annotated")
                    }
                },
            ),
        )
    }
}

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithSelectionPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
            text = TextFieldValue("Text input with selection", selection = TextRange(0, 4)),
        )
    }
}

@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithCompositionPreview() {
    PreviewWithThemes {
        AdvancedTextInput(
            onTextChange = {},
            text = TextFieldValue(
                text = "Text input with composition",
                composition = TextRange(0, 4),
            ),
        )
    }
}
+7 −0
Original line number Diff line number Diff line
package app.k9mail.core.ui.compose.designsystem.atom.textfield

import androidx.compose.runtime.Composable
import androidx.compose.ui.text.input.TextFieldValue

private val LINE_BREAK = "[\\r\\n]".toRegex()

@@ -8,6 +9,12 @@ internal fun stripLineBreaks(onValueChange: (String) -> Unit): (String) -> Unit
    onValueChange(value.replace(LINE_BREAK, replacement = ""))
}

internal fun stripTextFieldValueLineBreaks(onValueChange: (TextFieldValue) -> Unit): (TextFieldValue) -> Unit {
    return { value ->
        onValueChange(value.copy(text = value.text.replace(LINE_BREAK, replacement = "")))
    }
}

internal fun selectLabel(
    label: String?,
    isRequired: Boolean,
+33 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.material3.OutlinedTextField as Material3OutlinedTextField

@Suppress("LongParameterList")
@@ -33,3 +34,35 @@ fun TextFieldOutlined(
        keyboardOptions = keyboardOptions,
    )
}

/**
 * Overload of [TextFieldOutlined] that accepts a [TextFieldValue] instead of a [String].
 */
@Suppress("LongParameterList")
@Composable
fun TextFieldOutlined(
    value: TextFieldValue,
    onValueChange: (TextFieldValue) -> Unit,
    modifier: Modifier = Modifier,
    label: String? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    isEnabled: Boolean = true,
    isReadOnly: Boolean = false,
    isRequired: Boolean = false,
    hasError: Boolean = false,
    isSingleLine: Boolean = true,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
    Material3OutlinedTextField(
        value = value,
        onValueChange = if (isSingleLine) stripTextFieldValueLineBreaks(onValueChange) else onValueChange,
        modifier = modifier,
        enabled = isEnabled,
        label = selectLabel(label, isRequired),
        trailingIcon = trailingIcon,
        readOnly = isReadOnly,
        isError = hasError,
        singleLine = isSingleLine,
        keyboardOptions = keyboardOptions,
    )
}
+47 −0
Original line number Diff line number Diff line
package app.k9mail.core.ui.compose.designsystem.molecule.input

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlined

/**
 * A text input field that uses [TextFieldValue] to support text selection and composition.
 *
 * It supports annotated strings, which can be used to display rich text or formatted text.
 */
@Suppress("LongParameterList")
@Composable
fun AdvancedTextInput(
    onTextChange: (TextFieldValue) -> Unit,
    modifier: Modifier = Modifier,
    text: TextFieldValue = TextFieldValue(""),
    label: String? = null,
    isRequired: Boolean = false,
    errorMessage: String? = null,
    contentPadding: PaddingValues = inputContentPadding(),
    isSingleLine: Boolean = true,
    isEnabled: Boolean = true,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
    InputLayout(
        modifier = modifier,
        contentPadding = contentPadding,
        errorMessage = errorMessage,
    ) {
        TextFieldOutlined(
            value = text,
            onValueChange = onTextChange,
            label = label,
            isEnabled = isEnabled,
            isRequired = isRequired,
            hasError = errorMessage != null,
            isSingleLine = isSingleLine,
            modifier = Modifier.fillMaxWidth(),
            keyboardOptions = keyboardOptions,
        )
    }
}
+34 −7
Original line number Diff line number Diff line
@@ -4,14 +4,27 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.designsystem.molecule.input.TextInput
import app.k9mail.core.ui.compose.designsystem.molecule.input.AdvancedTextInput
import app.k9mail.core.ui.compose.theme2.MainTheme
import kotlinx.coroutines.delay
import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting

// This a workaround for a bug in Compose, preventing the keyboard been show when requesting focus on a dialog,
// see: https://issuetracker.google.com/issues/204502668
private const val EDIT_TEXT_FOCUS_DELAY = 200L

@Composable
internal fun PreferenceDialogTextView(
    preference: PreferenceSetting.Text,
@@ -20,13 +33,26 @@ internal fun PreferenceDialogTextView(
    onDismissRequest: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val currentText = rememberSaveable { mutableStateOf(preference.value) }
    val focusRequester = remember { FocusRequester() }
    var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(
            TextFieldValue(
                text = preference.value,
                selection = TextRange(preference.value.length),
            ),
        )
    }

    LaunchedEffect(Unit) {
        delay(EDIT_TEXT_FOCUS_DELAY)
        focusRequester.requestFocus()
    }

    PreferenceDialogLayout(
        title = preference.title(),
        icon = preference.icon(),
        onConfirmClick = {
            onConfirmClick(preference.copy(value = currentText.value))
            onConfirmClick(preference.copy(value = textFieldValue.text))
        },
        onDismissClick = onDismissClick,
        onDismissRequest = onDismissRequest,
@@ -38,12 +64,13 @@ internal fun PreferenceDialogTextView(
            Spacer(modifier = Modifier.height(MainTheme.spacings.default))
        }

        TextInput(
            text = currentText.value,
        AdvancedTextInput(
            text = textFieldValue,
            contentPadding = PaddingValues(),
            onTextChange = {
                currentText.value = it
            onTextChange = { changedText ->
                textFieldValue = changedText
            },
            modifier = Modifier.focusRequester(focusRequester),
        )
    }
}