mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-20 08:45:50 +01:00
Fix label font weight on focused fields.
This commit is contained in:
@@ -6,7 +6,9 @@
|
||||
package org.signal.core.ui.compose
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
@@ -20,6 +22,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -38,6 +41,7 @@ import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -188,6 +192,26 @@ object TextFields {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A text field label that sits at Label Medium weight once it has minimized above the input, which is what happens
|
||||
* when the field is focused or already has text in it.
|
||||
*
|
||||
* [interactionSource] must be the same instance given to the text field.
|
||||
*/
|
||||
@Composable
|
||||
fun Label(
|
||||
text: String,
|
||||
hasText: Boolean,
|
||||
interactionSource: InteractionSource
|
||||
) {
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
fontWeight = if (isFocused || hasText) FontWeight.Medium else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.createSelection(): TextRange {
|
||||
return when {
|
||||
isEmpty() -> TextRange.Zero
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@
|
||||
package org.signal.appsettings.totpnameentry
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -36,6 +37,7 @@ import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
|
||||
@VisibleForTesting
|
||||
object TotpNameEntryTestTags {
|
||||
@@ -53,6 +55,7 @@ fun TotpNameEntryScreen(
|
||||
onEvent: (TotpNameEntryEvent) -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
@@ -86,7 +89,8 @@ fun TotpNameEntryScreen(
|
||||
TextField(
|
||||
value = state.name,
|
||||
onValueChange = { onEvent(TotpNameEntryEvent.NameChanged(it)) },
|
||||
label = { Text(text = stringResource(R.string.TotpNameEntryScreen__name)) },
|
||||
label = { TextFields.Label(stringResource(R.string.TotpNameEntryScreen__name), state.name.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.submitting,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@
|
||||
package org.signal.registration.screens.addusername
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -65,6 +66,7 @@ import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Dividers
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.core.util.UsernameUtil
|
||||
import org.signal.libsignal.usernames.Username
|
||||
import org.signal.registration.R
|
||||
@@ -221,6 +223,7 @@ private fun ColumnScope.UsernameEntry(
|
||||
onEvent: (AddUsernameScreenEvents) -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val validationMessage: String? = state.validationError?.message()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -244,7 +247,8 @@ private fun ColumnScope.UsernameEntry(
|
||||
TextField(
|
||||
value = state.username,
|
||||
onValueChange = { onEvent(AddUsernameScreenEvents.UsernameChanged(it)) },
|
||||
label = { Text(stringResource(R.string.AddUsernameScreen__username)) },
|
||||
label = { TextFields.Label(stringResource(R.string.AddUsernameScreen__username), state.username.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.showSpinner,
|
||||
isError = state.validationError != null,
|
||||
|
||||
+5
-1
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.signal.registration.screens.aepentry
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -58,6 +59,7 @@ import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.passwordmanager.SignalCredentialManager
|
||||
import org.signal.passwordmanager.compose.attachPasswordAutoFillHelper
|
||||
import org.signal.passwordmanager.compose.passwordAutoFillHelper
|
||||
@@ -275,6 +277,7 @@ private fun RecoveryKeyTextField(state: EnterAepState, onEvent: (EnterAepEvents)
|
||||
var requestFocus by remember { mutableStateOf(true) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val autoFillHelper = passwordAutoFillHelper { onEvent(EnterAepEvents.BackupKeyChanged(it)) }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
TextField(
|
||||
value = state.recoveryKey.enteredText,
|
||||
@@ -282,7 +285,8 @@ private fun RecoveryKeyTextField(state: EnterAepState, onEvent: (EnterAepEvents)
|
||||
onEvent(EnterAepEvents.BackupKeyChanged(it))
|
||||
autoFillHelper.onValueChanged(it)
|
||||
},
|
||||
label = { Text(stringResource(R.string.EnterAepScreen__recovery_key)) },
|
||||
label = { TextFields.Label(stringResource(R.string.EnterAepScreen__recovery_key), state.recoveryKey.enteredText.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = MonoTypeface.fontFamily(),
|
||||
lineHeight = 36.sp
|
||||
|
||||
+9
-2
@@ -11,6 +11,7 @@ import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -50,6 +51,7 @@ import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.core.ui.rememberWindowBreakpoint
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
@@ -113,6 +115,9 @@ private fun CompactLayout(
|
||||
onAvatarClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val givenNameInteractionSource = remember { MutableInteractionSource() }
|
||||
val familyNameInteractionSource = remember { MutableInteractionSource() }
|
||||
|
||||
RegistrationScaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
@@ -152,7 +157,8 @@ private fun CompactLayout(
|
||||
OutlinedTextField(
|
||||
value = state.givenName,
|
||||
onValueChange = { onEvent(CreateProfileScreenEvents.GivenNameChanged(it)) },
|
||||
label = { Text(stringResource(R.string.CreateProfileScreen__first_name_required)) },
|
||||
label = { TextFields.Label(stringResource(R.string.CreateProfileScreen__first_name_required), state.givenName.isNotEmpty(), givenNameInteractionSource) },
|
||||
interactionSource = givenNameInteractionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.isSubmitting,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
@@ -169,7 +175,8 @@ private fun CompactLayout(
|
||||
OutlinedTextField(
|
||||
value = state.familyName,
|
||||
onValueChange = { onEvent(CreateProfileScreenEvents.FamilyNameChanged(it)) },
|
||||
label = { Text(stringResource(R.string.CreateProfileScreen__last_name_optional)) },
|
||||
label = { TextFields.Label(stringResource(R.string.CreateProfileScreen__last_name_optional), state.familyName.isNotEmpty(), familyNameInteractionSource) },
|
||||
interactionSource = familyNameInteractionSource,
|
||||
singleLine = true,
|
||||
enabled = !state.isSubmitting,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
|
||||
+5
-1
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.signal.registration.screens.localbackuprestore
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -51,6 +52,7 @@ import androidx.compose.ui.unit.sp
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.OnePaneRegistrationScaffold
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
@@ -233,13 +235,15 @@ private fun PassphraseTextField(
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var requestFocus by remember { mutableStateOf(true) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
TextField(
|
||||
value = passphrase,
|
||||
onValueChange = { newValue ->
|
||||
onPassphraseChange(newValue.filter { it.isDigit() })
|
||||
},
|
||||
label = { Text(stringResource(R.string.LocalBackupRestoreScreen__passphrase)) },
|
||||
label = { TextFields.Label(stringResource(R.string.LocalBackupRestoreScreen__passphrase), passphrase.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
lineHeight = 36.sp
|
||||
|
||||
+5
-1
@@ -14,6 +14,7 @@ import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -80,6 +81,7 @@ import org.signal.core.ui.compose.IconButtons.IconButton
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.registration.PendingRestoreOption
|
||||
@@ -515,6 +517,7 @@ private fun PhoneNumberInputFields(
|
||||
) {
|
||||
var phoneNumberTextFieldValue by remember { mutableStateOf(TextFieldValue(state.formattedNumber)) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val hasValidCountry = state.countryName.isNotEmpty()
|
||||
val isAccountId = state.enteredAccountId != null
|
||||
val label = if (isAccountId) R.string.RegistrationActivity_account_id else R.string.RegistrationActivity_phone_number_description
|
||||
@@ -594,7 +597,8 @@ private fun PhoneNumberInputFields(
|
||||
.weight(1f)
|
||||
.focusRequester(focusRequester)
|
||||
.testTag(TestTags.PHONE_NUMBER_PHONE_FIELD),
|
||||
label = { Text(stringResource(label)) },
|
||||
label = { TextFields.Label(stringResource(label), phoneNumberTextFieldValue.text.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
isError = state.isNumberInvalid || state.accountIdError != null,
|
||||
supportingText = supportingText,
|
||||
keyboardOptions = if (isAccountId) {
|
||||
|
||||
+9
-2
@@ -6,6 +6,7 @@
|
||||
package org.signal.registration.screens.signallogincredentials
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -65,6 +66,7 @@ import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.passwordmanager.SignalCredentialManager
|
||||
import org.signal.passwordmanager.compose.attachPasswordAutoFillHelper
|
||||
import org.signal.passwordmanager.compose.passwordAutoFillHelper
|
||||
@@ -302,10 +304,13 @@ private fun AccountIdTextField(
|
||||
onEvent: (SignalLoginCredentialEntryScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
TextField(
|
||||
value = state.accountId,
|
||||
onValueChange = { onEvent(SignalLoginCredentialEntryScreenEvents.AccountIdChanged(it)) },
|
||||
label = { Text(stringResource(R.string.SignalLoginCredentialEntryScreen__account_id)) },
|
||||
label = { TextFields.Label(stringResource(R.string.SignalLoginCredentialEntryScreen__account_id), state.accountId.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
singleLine = true,
|
||||
textStyle = accountIdTextStyle(),
|
||||
colors = TextFieldDefaults.colors(
|
||||
@@ -344,6 +349,7 @@ private fun RecoveryKeyTextField(
|
||||
if (revealed) AepVisualTransformation(RecoveryKeyGroups.GROUP_SIZE) else PasswordVisualTransformation()
|
||||
}
|
||||
val isError = state.recoveryKey.error != null || state.areCredentialsIncorrect
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val minimizedLabelHeight = MaterialTheme.typography.bodySmall.lineHeight.takeIf { it.isSp } ?: MINIMIZED_LABEL_LINE_HEIGHT
|
||||
val firstLineCenterY = with(LocalDensity.current) {
|
||||
TEXT_FIELD_TOP_PADDING + minimizedLabelHeight.toDp() + RECOVERY_KEY_LINE_HEIGHT.toDp() / 2
|
||||
@@ -356,7 +362,8 @@ private fun RecoveryKeyTextField(
|
||||
onEvent(SignalLoginCredentialEntryScreenEvents.RecoveryKeyChanged(it))
|
||||
autoFillHelper.onValueChanged(it)
|
||||
},
|
||||
label = { Text(stringResource(R.string.SignalLoginCredentialEntryScreen__recovery_key)) },
|
||||
label = { TextFields.Label(stringResource(R.string.SignalLoginCredentialEntryScreen__recovery_key), state.recoveryKey.enteredText.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
singleLine = !revealed,
|
||||
minLines = if (revealed) 3 else 1,
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
|
||||
+7
-1
@@ -8,6 +8,7 @@ package org.signal.registration.screens.signalloginpayment
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
@@ -60,6 +62,7 @@ import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.TextFields
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.OnePaneRegistrationScaffold
|
||||
@@ -451,12 +454,15 @@ private fun ManualReceiptCredentialEntry(
|
||||
state: SignalLoginPaymentState,
|
||||
onEvent: (SignalLoginPaymentScreenEvents) -> Unit
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TextField(
|
||||
value = state.manualReceiptCredential.value,
|
||||
onValueChange = { onEvent(SignalLoginPaymentScreenEvents.ManualReceiptCredentialChanged(ManualReceiptCredential(it))) },
|
||||
label = { Text(stringResource(R.string.SignalLoginPaymentScreen__paste_a_receipt_credential)) },
|
||||
label = { TextFields.Label(stringResource(R.string.SignalLoginPaymentScreen__paste_a_receipt_credential), state.manualReceiptCredential.value.isNotEmpty(), interactionSource) },
|
||||
interactionSource = interactionSource,
|
||||
enabled = !state.showSpinner,
|
||||
maxLines = 3,
|
||||
modifier = Modifier
|
||||
|
||||
Reference in New Issue
Block a user