Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recompositions due to autofill #975

Merged
merged 11 commits into from
Jul 8, 2023
16 changes: 7 additions & 9 deletions app/src/main/java/com/jerboa/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.Autofill
import androidx.compose.ui.autofill.AutofillNode
import androidx.compose.ui.autofill.AutofillTree
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
Expand All @@ -40,8 +41,6 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
Expand All @@ -64,6 +63,7 @@ import com.jerboa.ui.components.home.SiteViewModel
import com.jerboa.ui.components.inbox.InboxTab
import com.jerboa.ui.components.person.UserTab
import com.jerboa.ui.theme.SMALL_PADDING
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.ocpsoft.prettytime.PrettyTime
Expand Down Expand Up @@ -958,16 +958,14 @@ fun saveBitmapP(
}

@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.onAutofill(vararg autofillType: AutofillType, onFill: (String) -> Unit): Modifier = composed {
fun Modifier.onAutofill(tree: AutofillTree, autofill: Autofill?, autofillTypes: ImmutableList<AutofillType>, onFill: (String) -> Unit): Modifier {
val autofillNode = AutofillNode(
autofillTypes = autofillType.toList(),
autofillTypes = autofillTypes,
onFill = onFill,
)
LocalAutofillTree.current += autofillNode
tree += autofillNode

val autofill = LocalAutofill.current

this
return this
.onGloballyPositioned {
autofillNode.boundingBox = it.boundsInWindow()
}
Expand Down
136 changes: 73 additions & 63 deletions app/src/main/java/com/jerboa/ui/components/login/Login.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@file:OptIn(ExperimentalMaterial3Api::class)
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)

package com.jerboa.ui.components.login

Expand All @@ -22,6 +22,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
Expand All @@ -36,6 +38,8 @@ import com.jerboa.R
import com.jerboa.datatypes.types.Login
import com.jerboa.db.Account
import com.jerboa.onAutofill
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

@Composable
fun MyTextField(
Expand All @@ -44,7 +48,10 @@ fun MyTextField(
placeholder: String? = null,
text: String,
onValueChange: (String) -> Unit,
autofillTypes: ImmutableList<AutofillType> = persistentListOf(),
) {
var wasAutofilled by remember { mutableStateOf(false) }

OutlinedTextField(
value = text,
onValueChange = onValueChange,
Expand All @@ -56,20 +63,36 @@ fun MyTextField(
keyboardType = KeyboardType.Text,
autoCorrect = false,
),
modifier = modifier,
modifier = modifier
.background(if (wasAutofilled) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.onAutofill(LocalAutofillTree.current, LocalAutofill.current, autofillTypes) {
onValueChange(it)
wasAutofilled = true
},
)
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PasswordField(
modifier: Modifier = Modifier,
password: String,
onValueChange: (String) -> Unit,
) {
var wasAutofilled by remember { mutableStateOf(false) }
var passwordVisibility by remember { mutableStateOf(false) }

OutlinedTextField(
modifier = modifier,
modifier = modifier
.background(if (wasAutofilled) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.onAutofill(
LocalAutofillTree.current,
LocalAutofill.current,
persistentListOf(AutofillType.Password),
) {
onValueChange(it)
wasAutofilled = true
},
value = password,
onValueChange = onValueChange,
singleLine = true,
Expand All @@ -92,7 +115,50 @@ fun PasswordField(
)
}

@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun InstancePicker(expanded: Boolean, setExpanded: ((Boolean) -> Unit), instance: String, setInstance: ((String) -> Unit)) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
setExpanded(!expanded)
},
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
label = { Text(stringResource(R.string.login_instance)) },
placeholder = { Text(stringResource(R.string.login_instance_placeholder)) },
value = instance,
singleLine = true,
onValueChange = setInstance,
trailingIcon = {
TrailingIcon(expanded = expanded)
},
keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Uri),
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
setExpanded(false)
},
) {
DEFAULT_LEMMY_INSTANCES.forEach { selectionOption ->
DropdownMenuItem(
modifier = Modifier.exposedDropdownSize(),
text = {
Text(text = selectionOption)
},
onClick = {
setInstance(selectionOption)
setExpanded(false)
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LoginForm(
modifier: Modifier = Modifier,
Expand All @@ -103,9 +169,7 @@ fun LoginForm(
var username by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var totp by rememberSaveable { mutableStateOf("") }
val instanceOptions = DEFAULT_LEMMY_INSTANCES
var expanded by remember { mutableStateOf(false) }
var wasAutofilled by remember { mutableStateOf(false) }

val isValid =
instance.isNotEmpty() && username.isNotEmpty() && password.isNotEmpty()
Expand All @@ -123,77 +187,23 @@ fun LoginForm(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
label = { Text(stringResource(R.string.login_instance)) },
placeholder = { Text(stringResource(R.string.login_instance_placeholder)) },
value = instance,
singleLine = true,
onValueChange = { instance = it },
trailingIcon = {
TrailingIcon(expanded = expanded)
},
keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Uri),
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
},
) {
instanceOptions.forEach { selectionOption ->
DropdownMenuItem(
modifier = Modifier.exposedDropdownSize(),
text = {
Text(text = selectionOption)
},
onClick = {
instance = selectionOption
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
InstancePicker(expanded = expanded, { expanded = it }, instance, { instance = it })

MyTextField(
modifier = Modifier
.background(if (wasAutofilled) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.onAutofill(AutofillType.Username, AutofillType.EmailAddress) {
username = it
wasAutofilled = true
},
label = stringResource(R.string.login_email_or_username),
text = username,
onValueChange = { username = it },
autofillTypes = persistentListOf(AutofillType.Username, AutofillType.EmailAddress),
)
PasswordField(
modifier = Modifier
.background(if (wasAutofilled) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.onAutofill(AutofillType.Password) {
password = it
wasAutofilled = true
},
password = password,
onValueChange = { password = it },
)
MyTextField(
modifier = Modifier
.background(if (wasAutofilled) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
.onAutofill(AutofillType.SmsOtpCode) {
totp = it
wasAutofilled = true
},
label = stringResource(R.string.login_totp),
text = totp,
onValueChange = { totp = it },
autofillTypes = persistentListOf(AutofillType.SmsOtpCode),
)
Button(
enabled = isValid && !loading,
Expand Down