0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-19 19:42:20 +02:00

Implement internal text field highly inspired by #1897

This commit is contained in:
lm41 2024-09-16 15:45:57 +02:00
parent 2171e16346
commit fb967ea5bc
No known key found for this signature in database
5 changed files with 257 additions and 12 deletions

View File

@ -105,13 +105,6 @@ import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.devtools.flogInfo
import dev.patrickgold.florisboard.lib.devtools.flogWarning
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import org.florisboard.lib.snygg.ui.SnyggSurface
import org.florisboard.lib.snygg.ui.shape
import org.florisboard.lib.snygg.ui.snyggBackground
import org.florisboard.lib.snygg.ui.snyggBorder
import org.florisboard.lib.snygg.ui.snyggShadow
import org.florisboard.lib.snygg.ui.solidColor
import org.florisboard.lib.snygg.ui.spSize
import dev.patrickgold.florisboard.lib.util.ViewUtils
import dev.patrickgold.florisboard.lib.util.debugSummarize
import dev.patrickgold.florisboard.lib.util.launchActivity
@ -123,6 +116,13 @@ import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.systemServiceOrNull
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.snygg.ui.SnyggSurface
import org.florisboard.lib.snygg.ui.shape
import org.florisboard.lib.snygg.ui.snyggBackground
import org.florisboard.lib.snygg.ui.snyggBorder
import org.florisboard.lib.snygg.ui.snyggShadow
import org.florisboard.lib.snygg.ui.solidColor
import org.florisboard.lib.snygg.ui.spSize
import java.lang.ref.WeakReference
/**
@ -143,8 +143,14 @@ class FlorisImeService : LifecycleInputMethodService() {
private val InlineSuggestionUiSmallestSize = Size(0, 0)
private val InlineSuggestionUiBiggestSize = Size(Int.MAX_VALUE, Int.MAX_VALUE)
private var CurrentInputConnection: InputConnection? = null
fun setCurrentInputConnection(inputConnection: InputConnection?) {
CurrentInputConnection = inputConnection
}
fun currentInputConnection(): InputConnection? {
return FlorisImeServiceReference.get()?.currentInputConnection
return CurrentInputConnection ?: FlorisImeServiceReference.get()?.currentInputConnection
}
fun inputFeedbackController(): InputFeedbackController? {

View File

@ -46,16 +46,16 @@ import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
import org.florisboard.lib.android.isOrientationPortrait
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import org.florisboard.lib.snygg.SnyggLevel
import dev.patrickgold.florisboard.lib.util.VersionName
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
import dev.patrickgold.jetpref.datastore.model.PreferenceType
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.snygg.SnyggLevel
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
@ -179,6 +179,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "devtools__show_drag_and_drop_helpers",
default = false,
)
val enableInternalTextField = boolean(
key = "devtools__enable_internal_text_field",
default = false
)
}
val dictionary = Dictionary()

View File

@ -27,8 +27,6 @@ import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
@ -36,6 +34,8 @@ import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.android.showLongToast
class DebugOnPurposeCrashException : Exception(
"Success! The app crashed purposely to display this beautiful screen we all love :)"
@ -96,6 +96,12 @@ fun DevtoolsScreen() = FlorisScreen {
summary = stringRes(R.string.devtools__show_drag_and_drop_helpers__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.devtools.enableInternalTextField,
title = "Enable internal text field",
summary = "Enable the internal text field for testing purposes until it's correctly implemented in the new emoji layout",
enabledIf = { prefs.devtools.enabled isEqualTo true}
)
Preference(
title = stringRes(R.string.devtools__clear_udm_internal_database__label),
summary = stringRes(R.string.devtools__clear_udm_internal_database__summary),

View File

@ -26,6 +26,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -43,6 +46,7 @@ import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboardLayout
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.compose.FlorisInternalTextField
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.snygg.ui.solidColor
@ -55,6 +59,8 @@ fun TextInputLayout(
val prefs by florisPreferenceModel()
var internalText by rememberSaveable { mutableStateOf("") }
val state by keyboardManager.activeState.collectAsState()
val evaluator by keyboardManager.activeEvaluator.collectAsState()
@ -64,6 +70,12 @@ fun TextInputLayout(
.fillMaxWidth()
.wrapContentHeight(),
) {
if (prefs.devtools.enableInternalTextField.observeAsState().value) {
FlorisInternalTextField(
value = internalText,
onValueChange = { internalText = it },
)
}
Smartbar()
if (state.isActionsOverflowVisible) {
QuickActionsOverflowPanel()

View File

@ -0,0 +1,217 @@
package dev.patrickgold.florisboard.lib.compose
import android.graphics.Rect
import android.text.InputType
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import dev.patrickgold.florisboard.FlorisImeService
import dev.patrickgold.florisboard.lib.ValidationResult
@Composable
fun FlorisInternalTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
placeholder: String? = null,
isError: Boolean = false,
showValidationHint: Boolean = true,
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.colors(),
) {
val textColor = textStyle.color.takeOrElse {
if (enabled) {
colors.focusedTextColor
} else {
colors.disabledTextColor
}
}
val imeOptions: Int = when (keyboardOptions.imeAction) {
ImeAction.Done -> EditorInfo.IME_ACTION_DONE
ImeAction.Go -> EditorInfo.IME_ACTION_GO
ImeAction.Next -> EditorInfo.IME_ACTION_NEXT
ImeAction.Previous -> EditorInfo.IME_ACTION_PREVIOUS
ImeAction.Search -> EditorInfo.IME_ACTION_SEARCH
ImeAction.Send -> EditorInfo.IME_ACTION_SEND
else -> EditorInfo.IME_ACTION_NONE
}
val inputType: Int = when (keyboardOptions.keyboardType) {
KeyboardType.Text -> InputType.TYPE_CLASS_TEXT
KeyboardType.Number -> InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL
KeyboardType.Phone -> InputType.TYPE_CLASS_PHONE
KeyboardType.Uri -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
KeyboardType.Email -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
KeyboardType.Password -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
KeyboardType.NumberPassword -> InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
else -> InputType.TYPE_NULL
}
val inputCapitalization: Int = when (keyboardOptions.capitalization) {
KeyboardCapitalization.Characters -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
KeyboardCapitalization.Words -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
KeyboardCapitalization.Sentences -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
else -> 0
}
val mergedTextStyle = textStyle.copy(color = textColor, textDirection = TextDirection.Content)
val isFocused by interactionSource.collectIsFocusedAsState()
val isErrorState = isError || (showValidationError && validationResult?.isInvalid() == true)
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
Surface(
modifier = modifier,
color = if (enabled) { colors.focusedContainerColor} else { colors.disabledContainerColor},
border = if (isErrorState && enabled) {
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.error)
} else if (isFocused) {
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.primary)
} else {
ButtonDefaults.outlinedButtonBorder
},
shape = shape,
) {
Box(
modifier = Modifier
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = 40.dp,
)
.padding(ButtonDefaults.ContentPadding),
contentAlignment = Alignment.CenterStart,
) {
ProvideTextStyle(value = mergedTextStyle) {
val localTextStyle = LocalTextStyle.current
Row {
var localEditTextReference: EditText? by remember { mutableStateOf(null) }
AndroidView(
modifier = Modifier.fillMaxWidth(0.9f), // Occupy the max size in the Compose UI tree
factory = { context ->
object : EditText(context) {
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
if (text != null) {
onValueChange(text.toString())
}
}
public override fun onFocusChanged(
focused: Boolean,
direction: Int,
previouslyFocusedRect: Rect?
) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
if (focused)
FlorisImeService.setCurrentInputConnection(
onCreateInputConnection(
EditorInfo()
)
)
else
FlorisImeService.setCurrentInputConnection(null)
}
override fun onEditorAction(actionCode: Int) {
super.onEditorAction(actionCode)
clearFocus()
}
}.apply {
this.isEnabled = enabled
this.isSingleLine = singleLine
this.maxLines = maxLines
this.setTextColor(localTextStyle.color.toArgb())
localEditTextReference = this
this.imeOptions = imeOptions
this.inputType =
inputType or if (keyboardOptions.autoCorrect) InputType.TYPE_TEXT_FLAG_AUTO_CORRECT else 0 or inputCapitalization
}
},
update = {}
)
IconButton(onClick = {
localEditTextReference?.clearFocus()
}) {
Icon(Icons.Default.Check, null)
}
}
}
if (!placeholder.isNullOrBlank()) {
Text(
text = placeholder,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.56f),
)
}
}
}
}
/*if (showValidationHint && validationResult?.isValid() == true && validationResult.hasHintMessage()) {
Text(
text = validationResult.hintMessage(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.56f),
)
}
if (showValidationError && validationResult?.isInvalid() == true && validationResult.hasErrorMessage()) {
Text(
text = validationResult.errorMessage(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
}*/
}