mirror of
https://github.com/florisboard/florisboard.git
synced 2024-09-19 11:32:18 +02:00
Implement internal text field highly inspired by #1897
This commit is contained in:
parent
2171e16346
commit
fb967ea5bc
@ -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? {
|
||||
|
@ -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()
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
)
|
||||
}*/
|
||||
}
|
Loading…
Reference in New Issue
Block a user