Add support for autofilling login text fields (#293)
* Add support for autofilling login text fields Support autofilling login text fields via Android Autofill with the experimental AndroidX Compose API for it. Based on https://bryanherbst.com/2021/04/13/compose-autofill/ (with permission). Signed-off-by: Hans Christian Schmitz <git@hcsch.eu> * Move autofill implementation to designsystem library Signed-off-by: Hans Christian Schmitz <git@hcsch.eu> --------- Signed-off-by: Hans Christian Schmitz <git@hcsch.eu>
This commit is contained in:
parent
ad0886391b
commit
30a0da74f0
2 changed files with 46 additions and 2 deletions
|
|
@ -49,7 +49,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.autofill.AutofillType
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusDirection
|
import androidx.compose.ui.focus.FocusDirection
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
|
@ -77,6 +79,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.autofill
|
||||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
|
@ -206,6 +209,7 @@ internal fun ChangeServerSection(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun LoginForm(
|
internal fun LoginForm(
|
||||||
state: LoginRootState,
|
state: LoginRootState,
|
||||||
|
|
@ -239,7 +243,11 @@ internal fun LoginForm(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onTabOrEnterKeyFocusNext(focusManager)
|
.onTabOrEnterKeyFocusNext(focusManager)
|
||||||
.testTag(TestTags.loginEmailUsername),
|
.testTag(TestTags.loginEmailUsername)
|
||||||
|
.autofill(autofillTypes = listOf(AutofillType.Username), onFill = {
|
||||||
|
loginFieldState = it
|
||||||
|
eventSink(LoginRootEvents.SetLogin(it))
|
||||||
|
}),
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(R.string.screen_login_username_hint))
|
Text(text = stringResource(R.string.screen_login_username_hint))
|
||||||
},
|
},
|
||||||
|
|
@ -279,7 +287,11 @@ internal fun LoginForm(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onTabOrEnterKeyFocusNext(focusManager)
|
.onTabOrEnterKeyFocusNext(focusManager)
|
||||||
.testTag(TestTags.loginPassword),
|
.testTag(TestTags.loginPassword)
|
||||||
|
.autofill(autofillTypes = listOf(AutofillType.Password), onFill = {
|
||||||
|
passwordFieldState = it
|
||||||
|
eventSink(LoginRootEvents.SetPassword(it))
|
||||||
|
}),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
passwordFieldState = it
|
passwordFieldState = it
|
||||||
eventSink(LoginRootEvents.SetPassword(it))
|
eventSink(LoginRootEvents.SetPassword(it))
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,17 @@ import androidx.compose.material3.TextFieldColors
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.autofill.AutofillNode
|
||||||
|
import androidx.compose.ui.autofill.AutofillType
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.platform.LocalAutofill
|
||||||
|
import androidx.compose.ui.platform.LocalAutofillTree
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -118,3 +127,26 @@ private fun ContentToPreview() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
fun Modifier.autofill(autofillTypes: List<AutofillType>, onFill: (String) -> Unit) = composed {
|
||||||
|
val autofillNode = AutofillNode(autofillTypes, onFill = onFill)
|
||||||
|
LocalAutofillTree.current += autofillNode
|
||||||
|
|
||||||
|
val autofill = LocalAutofill.current
|
||||||
|
|
||||||
|
this
|
||||||
|
.onGloballyPositioned {
|
||||||
|
// Inform autofill framework of where our composable is so it can show the popup in the right place
|
||||||
|
autofillNode.boundingBox = it.boundsInWindow()
|
||||||
|
}
|
||||||
|
.onFocusChanged {
|
||||||
|
autofill?.run {
|
||||||
|
if (it.isFocused) {
|
||||||
|
requestAutofillForNode(autofillNode)
|
||||||
|
} else {
|
||||||
|
cancelAutofillForNode(autofillNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue