Skip to content

Commit 17e371d

Browse files
committed
Add autofill support
1 parent e1d04d3 commit 17e371d

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.hypergonial.chat.view.composables
2+
3+
import androidx.compose.ui.ExperimentalComposeUiApi
4+
import androidx.compose.ui.Modifier
5+
import androidx.compose.ui.autofill.AutofillNode
6+
import androidx.compose.ui.autofill.AutofillType
7+
import androidx.compose.ui.composed
8+
import androidx.compose.ui.focus.onFocusChanged
9+
import androidx.compose.ui.layout.boundsInWindow
10+
import androidx.compose.ui.layout.onGloballyPositioned
11+
import androidx.compose.ui.platform.LocalAutofill
12+
import androidx.compose.ui.platform.LocalAutofillTree
13+
14+
// Taken from https://bryanherbst.com/2021/04/13/compose-autofill/
15+
16+
/** Add autofill support to a composable. This is typically applied to text inputs.
17+
*
18+
* @param autofillTypes The types of autofill data that this field can accept.
19+
* @param onFill A callback that is called when the autofill data is filled into the field.
20+
* */
21+
@OptIn(ExperimentalComposeUiApi::class)
22+
fun Modifier.autofill(
23+
autofillTypes: List<AutofillType>,
24+
onFill: ((String) -> Unit),
25+
) = composed {
26+
val autofill = LocalAutofill.current
27+
val autofillNode = AutofillNode(onFill = onFill, autofillTypes = autofillTypes)
28+
LocalAutofillTree.current += autofillNode
29+
30+
this.onGloballyPositioned {
31+
autofillNode.boundingBox = it.boundsInWindow()
32+
}.onFocusChanged { focusState ->
33+
autofill?.run {
34+
if (focusState.isFocused) {
35+
requestAutofillForNode(autofillNode)
36+
} else {
37+
cancelAutofillForNode(autofillNode)
38+
}
39+
}
40+
}
41+
}

composeApp/src/commonMain/kotlin/com/hypergonial/chat/view/content/LoginContent.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ import androidx.compose.runtime.LaunchedEffect
2828
import androidx.compose.runtime.getValue
2929
import androidx.compose.runtime.remember
3030
import androidx.compose.ui.Alignment
31+
import androidx.compose.ui.ExperimentalComposeUiApi
3132
import androidx.compose.ui.Modifier
33+
import androidx.compose.ui.autofill.AutofillType
3234
import androidx.compose.ui.focus.FocusDirection
3335
import androidx.compose.ui.platform.LocalFocusManager
3436
import androidx.compose.ui.text.input.ImeAction
@@ -44,8 +46,7 @@ import com.hypergonial.chat.view.composables.ActionText
4446
import com.hypergonial.chat.view.composables.ChatButton
4547
import com.hypergonial.chat.view.composables.FullScreenProgressIndicator
4648
import com.hypergonial.chat.view.composables.PasswordTextField
47-
import com.hypergonial.chat.view.sendNotification
48-
import kotlin.random.Random
49+
import com.hypergonial.chat.view.composables.autofill
4950
import org.jetbrains.compose.resources.painterResource
5051

5152
@Composable
@@ -63,6 +64,7 @@ fun LoginBottomBar(component: LoginComponent) {
6364
}
6465
}
6566

67+
@OptIn(ExperimentalComposeUiApi::class)
6668
@Composable
6769
fun LoginContent(component: LoginComponent) {
6870
val state by component.data.subscribeAsState()
@@ -92,7 +94,10 @@ fun LoginContent(component: LoginComponent) {
9294
OutlinedTextField(
9395
value = state.username,
9496
isError = state.loginFailed,
95-
modifier = Modifier.width(300.dp).padding(0.dp, 5.dp),
97+
modifier =
98+
Modifier.width(300.dp)
99+
.padding(0.dp, 5.dp)
100+
.autofill(listOf(AutofillType.Username), onFill = { component.onUsernameChange(it) }),
96101
singleLine = true,
97102
enabled = !state.isLoggingIn,
98103
onValueChange = { component.onUsernameChange(username = it) },
@@ -113,6 +118,10 @@ fun LoginContent(component: LoginComponent) {
113118
value = state.password.expose(),
114119
isError = state.loginFailed,
115120
enabled = !state.isLoggingIn,
121+
modifier =
122+
Modifier.width(300.dp)
123+
.padding(0.dp, 5.dp)
124+
.autofill(listOf(AutofillType.Password), onFill = { component.onPasswordChange(it) }),
116125
label = { Text("Password") },
117126
placeholder = { Text("Enter your password...") },
118127
onValueChange = { component.onPasswordChange(password = it) },
@@ -123,7 +132,6 @@ fun LoginContent(component: LoginComponent) {
123132
if (state.canLogin) component.onLoginAttempt()
124133
}
125134
),
126-
modifier = Modifier.width(300.dp).padding(0.dp, 5.dp),
127135
)
128136

129137
ChatButton(
@@ -137,7 +145,7 @@ fun LoginContent(component: LoginComponent) {
137145
Text("Login")
138146
}
139147

140-
/* ChatButton(
148+
/* ChatButton(
141149
onClick = {
142150
sendNotification {
143151
id = Random.nextInt(0, Int.MAX_VALUE)

composeApp/src/commonMain/kotlin/com/hypergonial/chat/view/content/RegisterContent.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import androidx.compose.runtime.LaunchedEffect
3232
import androidx.compose.runtime.getValue
3333
import androidx.compose.runtime.remember
3434
import androidx.compose.ui.Alignment
35+
import androidx.compose.ui.ExperimentalComposeUiApi
3536
import androidx.compose.ui.Modifier
37+
import androidx.compose.ui.autofill.AutofillType
3638
import androidx.compose.ui.focus.FocusDirection
3739
import androidx.compose.ui.platform.LocalFocusManager
3840
import androidx.compose.ui.text.input.ImeAction
@@ -46,6 +48,7 @@ import com.hypergonial.chat.view.components.RegisterComponent
4648
import com.hypergonial.chat.view.composables.ChatButton
4749
import com.hypergonial.chat.view.composables.FullScreenProgressIndicator
4850
import com.hypergonial.chat.view.composables.PasswordTextField
51+
import com.hypergonial.chat.view.composables.autofill
4952

5053
@Composable
5154
fun RegisterTopBar(component: RegisterComponent) {
@@ -66,6 +69,7 @@ fun RegisterTopBar(component: RegisterComponent) {
6669
}
6770
}
6871

72+
@OptIn(ExperimentalComposeUiApi::class)
6973
@Composable
7074
fun RegisterContent(component: RegisterComponent) {
7175
val state by component.data.subscribeAsState()
@@ -89,7 +93,10 @@ fun RegisterContent(component: RegisterComponent) {
8993

9094
OutlinedTextField(
9195
value = state.username,
92-
modifier = Modifier.width(300.dp).padding(0.dp, 5.dp),
96+
modifier =
97+
Modifier.width(300.dp)
98+
.padding(0.dp, 5.dp)
99+
.autofill(listOf(AutofillType.NewUsername), onFill = { component.onPasswordChange(it) }),
93100
singleLine = true,
94101
isError = state.usernameErrors.isNotEmpty(),
95102
onValueChange = { component.onUsernameChange(it) },
@@ -135,7 +142,10 @@ fun RegisterContent(component: RegisterComponent) {
135142
keyboardType = KeyboardType.Password,
136143
imeAction = ImeAction.Next,
137144
),
138-
modifier = Modifier.width(300.dp).padding(0.dp, 5.dp),
145+
modifier =
146+
Modifier.width(300.dp)
147+
.padding(0.dp, 5.dp)
148+
.autofill(listOf(AutofillType.NewPassword), onFill = { component.onPasswordChange(it) }),
139149
)
140150

141151
for (error in state.passwordErrors) {
@@ -162,7 +172,10 @@ fun RegisterContent(component: RegisterComponent) {
162172
}
163173
}
164174
),
165-
modifier = Modifier.width(300.dp).padding(0.dp, 5.dp),
175+
modifier =
176+
Modifier.width(300.dp)
177+
.padding(0.dp, 5.dp)
178+
.autofill(listOf(AutofillType.NewPassword), onFill = { component.onPasswordChange(it) }),
166179
)
167180

168181
ChatButton(

0 commit comments

Comments
 (0)