Merge pull request #3834 from element-hq/feature/fga/design_system_text_field

Design system : implement new TextField
This commit is contained in:
ganfra 2024-11-12 11:18:07 +01:00 committed by GitHub
commit cffd2da10b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
188 changed files with 817 additions and 901 deletions

View file

@ -23,7 +23,6 @@ import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -36,16 +35,17 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.createroom.impl.R
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
import io.element.android.libraries.designsystem.components.LabelledTextField
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Scaffold
@ -217,7 +217,7 @@ private fun RoomNameWithAvatar(
modifier = Modifier.clickable(onClick = onAvatarClick),
)
LabelledTextField(
TextField(
label = stringResource(R.string.screen_create_room_room_name_label),
value = roomName,
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
@ -233,7 +233,7 @@ private fun RoomTopic(
onTopicChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LabelledTextField(
TextField(
modifier = modifier,
label = stringResource(R.string.screen_create_room_topic_label),
value = topic,
@ -322,51 +322,43 @@ private fun RoomAddressField(
onAddressChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.primary,
text = stringResource(R.string.screen_create_room_room_address_section_title),
)
TextField(
modifier = Modifier.fillMaxWidth(),
value = address.value,
leadingIcon = {
Text(
text = "#",
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
)
},
trailingIcon = {
Text(
text = homeserverName,
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
modifier = Modifier.padding(end = 16.dp)
)
},
supportingText = {
Text(
text = stringResource(R.string.screen_create_room_room_address_section_footer),
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
)
},
onValueChange = onAddressChange,
singleLine = true,
)
}
TextField(
modifier = modifier.fillMaxWidth(),
value = address.value,
label = stringResource(R.string.screen_create_room_room_address_section_title),
leadingIcon = {
Text(
text = "#",
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
)
},
trailingIcon = {
Text(
text = homeserverName,
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
)
},
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
onValueChange = onAddressChange,
singleLine = true,
)
}
@PreviewsDayNight
@PreviewWithLargeHeight
@Composable
internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview {
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewLight { ContentToPreview(state) }
@PreviewWithLargeHeight
@Composable
internal fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewDark { ContentToPreview(state) }
@ExcludeFromCoverage
@Composable
private fun ContentToPreview(state: ConfigureRoomState) {
ConfigureRoomView(
state = state,
onBackClick = {},

View file

@ -9,7 +9,9 @@
package io.element.android.features.logout.impl
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
@ -52,19 +54,18 @@ import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrgani
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.list.SwitchListItem
import io.element.android.libraries.designsystem.modifiers.autofill
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
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.TextField
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.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@ -257,26 +258,21 @@ private fun Content(
)
}
Column(
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
) {
Text(
text = stringResource(CommonStrings.action_confirm_password),
style = ElementTheme.typography.fontBodySmMedium,
color = ElementTheme.colors.textSecondary,
)
var passwordVisible by remember { mutableStateOf(false) }
if (isLoading) {
// Ensure password is hidden when user submits the form
passwordVisible = false
}
OutlinedTextField(
TextField(
value = passwordFieldState,
label = stringResource(CommonStrings.action_confirm_password),
readOnly = isLoading,
modifier = Modifier
.padding(top = 8.dp)
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(focusManager)
.testTag(TestTags.loginPassword)
@ -293,9 +289,7 @@ private fun Content(
passwordFieldState = sanitized
eventSink(AccountDeactivationEvents.SetPassword(sanitized))
},
placeholder = {
Text(text = stringResource(CommonStrings.common_password))
},
placeholder = stringResource(CommonStrings.common_password),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image =
@ -303,7 +297,7 @@ private fun Content(
val description =
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Box(modifier = Modifier.clickable { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
}
},

View file

@ -60,8 +60,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
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.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.RoomType
@ -390,19 +390,13 @@ private fun DefaultLoadedContent(
)
} else if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
Spacer(modifier = Modifier.height(24.dp))
OutlinedTextField(
TextField(
value = knockMessage,
onValueChange = onKnockMessageUpdate,
maxLines = 3,
minLines = 3,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(R.string.screen_join_room_knock_message_description),
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textPlaceholder,
textAlign = TextAlign.Start,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
supportingText = stringResource(R.string.screen_join_room_knock_message_description)
)
}
}

View file

@ -7,6 +7,7 @@
package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@ -52,17 +53,15 @@ import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.modifiers.autofill
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
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.TextField
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.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@ -101,12 +100,12 @@ fun LoginPasswordView(
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(padding)
.consumeWindowInsets(padding)
.verticalScroll(state = scrollState)
.padding(start = 20.dp, end = 20.dp, bottom = 20.dp),
.fillMaxSize()
.imePadding()
.padding(padding)
.consumeWindowInsets(padding)
.verticalScroll(state = scrollState)
.padding(start = 20.dp, end = 20.dp, bottom = 20.dp),
) {
// Title
IconTitleSubtitleMolecule(
@ -140,8 +139,8 @@ fun LoginPasswordView(
onClick = ::submit,
enabled = state.submitEnabled || isLoading,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
.fillMaxWidth()
.testTag(TestTags.loginContinue)
)
Spacer(modifier = Modifier.height(48.dp))
}
@ -170,16 +169,10 @@ private fun LoginForm(
val eventSink = state.eventSink
Column {
Text(
text = stringResource(R.string.screen_login_form_header),
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
TextField(
label = stringResource(R.string.screen_login_form_header),
value = loginFieldState,
readOnly = isLoading,
enabled = !isLoading,
modifier = Modifier
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(focusManager)
@ -192,9 +185,7 @@ private fun LoginForm(
eventSink(LoginPasswordEvents.SetLogin(sanitized))
}
),
placeholder = {
Text(text = stringResource(CommonStrings.common_username))
},
placeholder = stringResource(CommonStrings.common_username),
onValueChange = {
val sanitized = it.sanitize()
loginFieldState = sanitized
@ -210,10 +201,14 @@ private fun LoginForm(
singleLine = true,
trailingIcon = if (loginFieldState.isNotEmpty()) {
{
IconButton(onClick = {
Box(Modifier.clickable {
loginFieldState = ""
}) {
Icon(imageVector = CompoundIcons.Close(), contentDescription = stringResource(CommonStrings.action_clear))
Icon(
imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_clear),
tint = ElementTheme.colors.iconSecondary
)
}
}
} else {
@ -226,9 +221,9 @@ private fun LoginForm(
passwordVisible = false
}
Spacer(Modifier.height(20.dp))
OutlinedTextField(
TextField(
value = passwordFieldState,
readOnly = isLoading,
enabled = !isLoading,
modifier = Modifier
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(focusManager)
@ -246,18 +241,18 @@ private fun LoginForm(
passwordFieldState = sanitized
eventSink(LoginPasswordEvents.SetPassword(sanitized))
},
placeholder = {
Text(text = stringResource(CommonStrings.common_password))
},
placeholder = stringResource(CommonStrings.common_password),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image =
if (passwordVisible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
val description =
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
Box(Modifier.clickable { passwordVisible = !passwordVisible }) {
Icon(
imageVector = image,
contentDescription = description,
)
}
},
keyboardOptions = KeyboardOptions(

View file

@ -9,6 +9,7 @@
package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
@ -23,7 +24,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@ -51,14 +51,12 @@ import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubti
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@ -86,10 +84,10 @@ fun SearchAccountProviderView(
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(padding)
.consumeWindowInsets(padding)
.fillMaxSize()
.imePadding()
.padding(padding)
.consumeWindowInsets(padding)
) {
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
item {
@ -104,7 +102,7 @@ fun SearchAccountProviderView(
// TextInput
var userInputState by textFieldState(stateValue = state.userInput)
val focusManager = LocalFocusManager.current
OutlinedTextField(
TextField(
value = userInputState,
// readOnly = isLoading,
modifier = Modifier
@ -126,7 +124,7 @@ fun SearchAccountProviderView(
singleLine = true,
trailingIcon = if (userInputState.isNotEmpty()) {
{
IconButton(onClick = {
Box(Modifier.clickable {
userInputState = ""
eventSink(SearchAccountProviderEvents.UserInput(""))
}) {
@ -139,9 +137,7 @@ fun SearchAccountProviderView(
} else {
null
},
supportingText = {
Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary)
}
supportingText = stringResource(id = R.string.screen_account_provider_form_notice),
)
}

View file

@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
@ -39,9 +38,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
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.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.ui.strings.CommonStrings
@ -89,21 +88,16 @@ fun ReportMessageView(
) {
Spacer(modifier = Modifier.height(20.dp))
OutlinedTextField(
TextField(
value = state.reason,
onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) },
placeholder = { Text(stringResource(R.string.screen_report_content_hint)) },
placeholder = stringResource(R.string.screen_report_content_hint),
minLines = 3,
enabled = !isSending,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 90.dp)
)
Text(
text = stringResource(R.string.screen_report_content_explanation),
style = ElementTheme.typography.fontBodySmRegular,
color = MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Start,
modifier = Modifier.padding(top = 4.dp, bottom = 24.dp, start = 16.dp, end = 16.dp)
.heightIn(min = 90.dp),
supportingText = stringResource(R.string.screen_report_content_explanation),
)
Row(

View file

@ -33,7 +33,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.poll.impl.R
@ -48,10 +47,10 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
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.TextButton
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.ui.strings.CommonStrings
@ -109,14 +108,10 @@ fun CreatePollView(
) {
item {
Column {
Text(
text = stringResource(id = R.string.screen_create_poll_question_desc),
modifier = Modifier.padding(start = 32.dp),
style = ElementTheme.typography.fontBodyMdRegular,
)
ListItem(
headlineContent = {
OutlinedTextField(
TextField(
label = stringResource(id = R.string.screen_create_poll_question_desc),
value = state.question,
onValueChange = {
state.eventSink(CreatePollEvents.SetQuestion(it))
@ -124,9 +119,7 @@ fun CreatePollView(
modifier = Modifier
.focusRequester(questionFocusRequester)
.fillMaxWidth(),
placeholder = {
Text(text = stringResource(id = R.string.screen_create_poll_question_hint))
},
placeholder = stringResource(id = R.string.screen_create_poll_question_hint),
keyboardOptions = keyboardOptions,
)
}
@ -137,7 +130,7 @@ fun CreatePollView(
val isLastItem = index == state.answers.size - 1
ListItem(
headlineContent = {
OutlinedTextField(
TextField(
value = answer.text,
onValueChange = {
state.eventSink(CreatePollEvents.SetAnswer(index, it))
@ -145,9 +138,7 @@ fun CreatePollView(
modifier = Modifier
.then(if (isLastItem) Modifier.focusRequester(answerFocusRequester) else Modifier)
.fillMaxWidth(),
placeholder = {
Text(text = stringResource(id = R.string.screen_create_poll_answer_hint, index + 1))
},
placeholder = stringResource(id = R.string.screen_create_poll_answer_hint, index + 1),
keyboardOptions = keyboardOptions,
)
},

View file

@ -29,7 +29,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.preferences.impl.R
import io.element.android.libraries.designsystem.components.LabelledOutlinedTextField
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -41,6 +40,7 @@ import io.element.android.libraries.designsystem.theme.aliasScreenTitle
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.TextButton
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.EditableAvatarView
@ -112,7 +112,7 @@ fun EditUserProfileView(
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(40.dp))
LabelledOutlinedTextField(
TextField(
label = stringResource(R.string.screen_edit_profile_display_name),
value = state.displayName,
placeholder = stringResource(CommonStrings.common_room_name_placeholder),

View file

@ -39,13 +39,12 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@ -70,17 +69,14 @@ fun BugReportView(
)
Spacer(modifier = Modifier.height(16.dp))
PreferenceRow {
OutlinedTextField(
TextField(
value = descriptionFieldState,
modifier = Modifier.fillMaxWidth()
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
modifier = Modifier
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
enabled = isFormEnabled,
label = {
Text(text = stringResource(id = R.string.screen_bug_report_editor_placeholder))
},
supportingText = {
Text(text = stringResource(id = R.string.screen_bug_report_editor_description))
},
placeholder = stringResource(id = R.string.screen_bug_report_editor_placeholder),
supportingText = stringResource(id = R.string.screen_bug_report_editor_description),
onValueChange = {
descriptionFieldState = it
eventSink(BugReportEvents.SetDescription(it))
@ -152,8 +148,8 @@ fun BugReportView(
enabled = state.submitEnabled,
showProgress = state.sending.isLoading(),
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 16.dp)
.fillMaxWidth()
.padding(top = 24.dp, bottom = 16.dp)
)
}
}

View file

@ -9,7 +9,6 @@
package io.element.android.features.roomdetails.impl.edit
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -21,7 +20,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -33,7 +31,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.designsystem.components.LabelledTextField
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -45,6 +42,7 @@ import io.element.android.libraries.designsystem.theme.aliasScreenTitle
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.TextButton
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.EditableAvatarView
@ -110,40 +108,28 @@ fun RoomDetailsEditView(
)
Spacer(modifier = Modifier.height(60.dp))
if (state.canChangeName) {
LabelledTextField(
label = stringResource(id = R.string.screen_room_details_room_name_label),
value = state.roomRawName,
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
singleLine = true,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) },
)
} else {
LabelledReadOnlyField(
title = stringResource(R.string.screen_room_details_room_name_label),
value = state.roomRawName
)
}
TextField(
label = stringResource(id = R.string.screen_room_details_room_name_label),
value = state.roomRawName,
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
singleLine = true,
readOnly = !state.canChangeName,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) },
)
Spacer(modifier = Modifier.height(28.dp))
if (state.canChangeTopic) {
LabelledTextField(
label = stringResource(CommonStrings.common_topic),
value = state.roomTopic,
placeholder = stringResource(CommonStrings.common_topic_placeholder),
maxLines = 10,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
),
)
} else {
LabelledReadOnlyField(
title = stringResource(R.string.screen_room_details_topic_title),
value = state.roomTopic
)
}
TextField(
label = stringResource(CommonStrings.common_topic),
value = state.roomTopic,
placeholder = stringResource(CommonStrings.common_topic_placeholder),
maxLines = 10,
readOnly = !state.canChangeTopic,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
),
)
}
}
@ -171,30 +157,6 @@ fun RoomDetailsEditView(
)
}
@Composable
private fun LabelledReadOnlyField(
title: String,
value: String,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.primary,
text = title,
)
Text(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 12.dp),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
text = value,
)
}
}
@PreviewsDayNight
@Composable
internal fun RoomDetailsEditViewPreview(@PreviewParameter(RoomDetailsEditStateProvider::class) state: RoomDetailsEditState) = ElementPreview {

View file

@ -9,9 +9,9 @@ package io.element.android.features.roomdetails.edit
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertHasNoClickAction
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.isEditable
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
@ -78,7 +78,6 @@ class RoomDetailsEditViewTest {
roomRawName = "Marketing",
),
)
rule.onNodeWithText("Marketing").assertHasClickAction()
rule.onNodeWithText("Marketing").performTextInput("A")
eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomName("AMarketing"))
}
@ -93,7 +92,7 @@ class RoomDetailsEditViewTest {
canChangeName = false,
),
)
rule.onNodeWithText("Marketing").assertHasNoClickAction()
rule.onNodeWithText("Marketing").assert(!isEditable())
}
@Test
@ -105,7 +104,6 @@ class RoomDetailsEditViewTest {
roomTopic = "My Topic",
),
)
rule.onNodeWithText("My Topic").assertHasClickAction()
rule.onNodeWithText("My Topic").performTextInput("A")
eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomTopic("AMy Topic"))
}
@ -120,7 +118,7 @@ class RoomDetailsEditViewTest {
canChangeTopic = false,
),
)
rule.onNodeWithText("My Topic").assertHasNoClickAction()
rule.onNodeWithText("My Topic").assert(!isEditable())
}
@Ignore("This test is failing because the bottom sheet does not open")

View file

@ -45,11 +45,11 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.FilledTextField
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
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.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings
@ -194,7 +194,7 @@ private fun SearchTextField(
),
) {
val focusManager = LocalFocusManager.current
TextField(
FilledTextField(
modifier = modifier.testTag(TestTags.searchTextField.value),
textStyle = ElementTheme.typography.fontBodyLgRegular,
singleLine = true,

View file

@ -49,11 +49,11 @@ import io.element.android.libraries.designsystem.modifiers.applyIf
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.FilledTextField
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
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.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.copy
import io.element.android.libraries.matrix.api.core.RoomId
@ -130,7 +130,7 @@ private fun RoomListSearchContent(
title = {
val filter = state.query
val focusRequester = FocusRequester()
TextField(
FilledTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),

View file

@ -7,6 +7,8 @@
package io.element.android.features.securebackup.impl.reset.password
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -25,14 +27,12 @@ import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@ -80,14 +80,13 @@ fun ResetIdentityPasswordView(
@Composable
private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Boolean) {
var showPassword by remember { mutableStateOf(false) }
OutlinedTextField(
TextField(
modifier = Modifier
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
value = text,
onValueChange = onTextChange,
label = { Text(stringResource(CommonStrings.common_password)) },
placeholder = { Text(stringResource(R.string.screen_reset_encryption_password_placeholder)) },
placeholder = stringResource(CommonStrings.common_password),
singleLine = true,
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
@ -96,13 +95,13 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
val description =
if (showPassword) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
IconButton(onClick = { showPassword = !showPassword }) {
Box(Modifier.clickable { showPassword = !showPassword }) {
Icon(imageVector = image, description)
}
},
isError = hasError,
supportingText = if (hasError) {
{ Text(stringResource(R.string.screen_reset_encryption_password_error)) }
stringResource(R.string.screen_reset_encryption_password_error)
} else {
null
}

View file

@ -36,14 +36,14 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.securebackup.impl.R
import io.element.android.features.securebackup.impl.tools.RecoveryKeyVisualTransformation
import io.element.android.libraries.designsystem.modifiers.autofill
import io.element.android.libraries.designsystem.modifiers.clickableIfNotNull
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.autofill
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@ -62,7 +62,6 @@ internal fun RecoveryKeyView(
) {
Text(
text = stringResource(id = CommonStrings.common_recovery_key),
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
)
RecoveryKeyContent(state, onClick, onChange, onSubmit)
@ -159,7 +158,7 @@ private fun RecoveryKeyFormContent(
// Do not apply a visual transformation if the key has spaces, to let user enter passphrase
if (keyHasSpace) VisualTransformation.None else RecoveryKeyVisualTransformation()
}
OutlinedTextField(
TextField(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.recoveryKey)
@ -179,7 +178,7 @@ private fun RecoveryKeyFormContent(
keyboardActions = KeyboardActions(
onDone = { onSubmit() }
),
label = { Text(text = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder)) }
placeholder = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder),
)
}
@ -198,14 +197,12 @@ private fun RecoveryKeyFooter(state: RecoveryKeyViewState) {
}
),
color = ElementTheme.colors.textSecondary,
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodySmRegular,
)
} else {
Text(
text = stringResource(id = R.string.screen_recovery_key_save_key_description),
color = ElementTheme.colors.textSecondary,
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodySmRegular,
)
}
@ -214,7 +211,6 @@ private fun RecoveryKeyFooter(state: RecoveryKeyViewState) {
Text(
text = stringResource(id = R.string.screen_recovery_key_confirm_key_description),
color = ElementTheme.colors.textSecondary,
modifier = Modifier.padding(start = 16.dp),
style = ElementTheme.typography.fontBodySmRegular,
)
}

View file

@ -1,76 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.designsystem.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
fun LabelledOutlinedTextField(
label: String,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
placeholder: String? = null,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.primary,
text = label
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = value,
placeholder = placeholder?.let { { Text(placeholder) } },
onValueChange = onValueChange,
singleLine = singleLine,
maxLines = maxLines,
keyboardOptions = keyboardOptions,
)
}
}
@PreviewsDayNight
@Composable
internal fun LabelledOutlinedTextFieldPreview() = ElementPreview {
Column {
LabelledOutlinedTextField(
label = "Room name",
value = "",
onValueChange = {},
placeholder = "e.g. Product Sprint",
)
LabelledOutlinedTextField(
label = "Room name",
value = "a room name",
onValueChange = {},
placeholder = "e.g. Product Sprint",
)
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.designsystem.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextField
@Composable
fun LabelledTextField(
label: String,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
placeholder: String? = null,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.primary,
text = label
)
TextField(
modifier = Modifier.fillMaxWidth(),
value = value,
placeholder = placeholder?.let { { Text(placeholder) } },
onValueChange = onValueChange,
singleLine = singleLine,
maxLines = maxLines,
keyboardOptions = keyboardOptions,
)
}
}
@PreviewsDayNight
@Composable
internal fun LabelledTextFieldPreview() = ElementPreview {
Column {
LabelledTextField(
label = "Room name",
value = "",
onValueChange = {},
placeholder = "e.g. Product Sprint",
)
LabelledTextField(
label = "Room name",
value = "a room name",
onValueChange = {},
placeholder = "e.g. Product Sprint",
)
}
}

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.designsystem.components.list
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -18,7 +19,6 @@ import androidx.compose.ui.tooling.preview.Preview
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text
@Composable

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.designsystem.modifiers
import androidx.compose.ui.ExperimentalComposeUiApi
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.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
@Suppress("ModifierComposed")
@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)
}
}
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.designsystem.modifiers
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
fun Modifier.onTabOrEnterKeyFocusNext(focusManager: FocusManager): Modifier = onPreviewKeyEvent { event ->
if (event.key == Key.Tab || event.key == Key.Enter) {
if (event.type == KeyEventType.KeyUp) {
focusManager.moveFocus(FocusDirection.Down)
}
true
} else {
false
}
}

View file

@ -15,25 +15,19 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
@ -42,7 +36,7 @@ import io.element.android.libraries.designsystem.utils.allBooleans
import io.element.android.libraries.designsystem.utils.asInt
@Composable
fun OutlinedTextField(
fun FilledTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
@ -60,12 +54,16 @@ fun OutlinedTextField(
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = OutlinedTextFieldDefaults.shape,
colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
shape: Shape = TextFieldDefaults.shape,
colors: TextFieldColors = TextFieldDefaults.colors(
unfocusedContainerColor = ElementTheme.colors.bgSubtleSecondary,
focusedContainerColor = ElementTheme.colors.bgSubtleSecondary,
disabledContainerColor = ElementTheme.colors.bgSubtleSecondary,
errorContainerColor = ElementTheme.colors.bgSubtleSecondary,
)
) {
androidx.compose.material3.OutlinedTextField(
androidx.compose.material3.TextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
@ -83,7 +81,6 @@ fun OutlinedTextField(
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
minLines = minLines,
interactionSource = interactionSource,
shape = shape,
colors = colors,
@ -91,7 +88,7 @@ fun OutlinedTextField(
}
@Composable
fun OutlinedTextField(
fun FilledTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
@ -108,12 +105,12 @@ fun OutlinedTextField(
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = OutlinedTextFieldDefaults.shape,
colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
shape: Shape = TextFieldDefaults.shape,
colors: TextFieldColors = TextFieldDefaults.colors()
) {
androidx.compose.material3.OutlinedTextField(
androidx.compose.material3.TextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
@ -137,37 +134,62 @@ fun OutlinedTextField(
)
}
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.onTabOrEnterKeyFocusNext(focusManager: FocusManager): Modifier = onPreviewKeyEvent { event ->
if (event.key == Key.Tab || event.key == Key.Enter) {
if (event.type == KeyEventType.KeyUp) {
focusManager.moveFocus(FocusDirection.Down)
}
true
} else {
false
}
}
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun FilledTextFieldLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun OutlinedTextFieldsPreview() = ElementPreviewLight { ContentToPreview() }
internal fun FilledTextFieldDarkPreview() =
ElementPreviewDark { ContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun OutlinedTextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
@ExcludeFromCoverage
@Composable
private fun ContentToPreview() {
Column(modifier = Modifier.padding(4.dp)) {
allBooleans.forEach { isError ->
allBooleans.forEach { enabled ->
allBooleans.forEach { readonly ->
OutlinedTextField(
FilledTextField(
value = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
onValueChange = {},
label = { Text(text = "label") },
isError = isError,
enabled = enabled,
readOnly = readonly,
)
Spacer(modifier = Modifier.height(2.dp))
}
}
}
}
}
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun FilledTextFieldValueLightPreview() =
ElementPreviewLight { FilledTextFieldValueContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun FilledTextFieldValueTextFieldDarkPreview() =
ElementPreviewDark { FilledTextFieldValueContentToPreview() }
@ExcludeFromCoverage
@Composable
private fun FilledTextFieldValueContentToPreview() {
Column(modifier = Modifier.padding(4.dp)) {
allBooleans.forEach { isError ->
allBooleans.forEach { enabled ->
allBooleans.forEach { readonly ->
FilledTextField(
value = TextFieldValue(
text = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
selection = TextRange(0, "Hello".length),
),
onValueChange = {},
label = { Text(text = "label") },
value = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
isError = isError,
enabled = enabled,
readOnly = readonly,

View file

@ -7,36 +7,37 @@
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
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.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.TextRange
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
@ -44,56 +45,63 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.allBooleans
import io.element.android.libraries.designsystem.utils.asInt
/**
* https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2008-37137
*/
@Composable
fun TextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
label: String? = null,
supportingText: String? = null,
placeholder: String? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
supportingText: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
enabled: Boolean = true,
readOnly: Boolean = false,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = TextFieldDefaults.shape,
colors: TextFieldColors = TextFieldDefaults.colors(
unfocusedContainerColor = ElementTheme.colors.bgSubtleSecondary,
focusedContainerColor = ElementTheme.colors.bgSubtleSecondary,
disabledContainerColor = ElementTheme.colors.bgSubtleSecondary,
errorContainerColor = ElementTheme.colors.bgSubtleSecondary,
)
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
androidx.compose.material3.TextField(
val isFocused by interactionSource.collectIsFocusedAsState()
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
textStyle = textFieldStyle(enabled),
interactionSource = interactionSource,
enabled = enabled,
readOnly = readOnly,
textStyle = textStyle,
label = label,
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
supportingText = supportingText,
isError = isError,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
interactionSource = interactionSource,
shape = shape,
colors = colors,
)
minLines = minLines,
readOnly = readOnly,
cursorBrush = SolidColor(ElementTheme.colors.textPrimary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout,
) { innerTextField ->
DecorationBox(
label = label,
readOnly = readOnly,
enabled = enabled,
isFocused = isFocused,
isError = isError,
leadingIcon = leadingIcon,
placeholder = placeholder,
isTextEmpty = value.isEmpty(),
innerTextField = innerTextField,
trailingIcon = trailingIcon,
supportingText = supportingText
)
}
}
@Composable
@ -101,69 +109,200 @@ fun TextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
label: String? = null,
supportingText: String? = null,
placeholder: String? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
supportingText: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
enabled: Boolean = true,
readOnly: Boolean = false,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = TextFieldDefaults.shape,
colors: TextFieldColors = TextFieldDefaults.colors()
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
androidx.compose.material3.TextField(
val isFocused by interactionSource.collectIsFocusedAsState()
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
textStyle = textFieldStyle(enabled),
interactionSource = interactionSource,
enabled = enabled,
readOnly = readOnly,
textStyle = textStyle,
label = label,
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
supportingText = supportingText,
isError = isError,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
interactionSource = interactionSource,
shape = shape,
colors = colors,
minLines = minLines,
readOnly = readOnly,
cursorBrush = SolidColor(ElementTheme.colors.textPrimary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout,
) { innerTextField ->
DecorationBox(
label = label,
readOnly = readOnly,
enabled = enabled,
isFocused = isFocused,
isError = isError,
leadingIcon = leadingIcon,
placeholder = placeholder,
isTextEmpty = value.text.isEmpty(),
innerTextField = innerTextField,
trailingIcon = trailingIcon,
supportingText = supportingText
)
}
}
@Composable
private fun DecorationBox(
label: String?,
enabled: Boolean,
readOnly: Boolean,
isFocused: Boolean,
isError: Boolean,
placeholder: String?,
isTextEmpty: Boolean,
supportingText: String?,
leadingIcon: @Composable (() -> Unit)?,
trailingIcon: @Composable (() -> Unit)?,
innerTextField: @Composable () -> Unit,
) {
Column {
if (label != null) {
Text(
text = label,
color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyMdRegular,
)
Spacer(modifier = Modifier.height(8.dp))
}
TextFieldContainer(
enabled = enabled,
readOnly = readOnly,
isFocused = isFocused,
isError = isError
) {
Row(modifier = Modifier.padding(16.dp)) {
if (leadingIcon != null) {
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.iconSecondary) {
leadingIcon()
}
Spacer(modifier = Modifier.width(8.dp))
}
Box(modifier = Modifier.weight(1f)) {
if (placeholder != null && isTextEmpty) {
Text(
text = placeholder,
color = ElementTheme.colors.textPlaceholder,
style = ElementTheme.typography.fontBodyLgRegular,
)
}
innerTextField()
}
if (trailingIcon != null) {
Spacer(modifier = Modifier.width(8.dp))
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.iconSecondary) {
trailingIcon()
}
}
}
}
if (supportingText != null) {
Spacer(modifier = Modifier.height(4.dp))
SupportingTextLayout(isError, supportingText)
}
}
}
@Composable
private fun TextFieldContainer(
enabled: Boolean,
readOnly: Boolean,
isFocused: Boolean,
isError: Boolean,
content: @Composable () -> Unit
) {
Surface(
shape = RoundedCornerShape(4.dp),
border = if (readOnly) {
null
} else {
BorderStroke(
width = if (isFocused) 2.dp else 1.dp,
color = when {
!enabled -> ElementTheme.colors.borderDisabled
isError -> ElementTheme.colors.borderCriticalPrimary
isFocused -> ElementTheme.colors.borderInteractiveHovered
else -> ElementTheme.colors.borderInteractiveSecondary
}
)
},
color = when {
readOnly -> ElementTheme.colors.bgSubtleSecondary
!enabled -> ElementTheme.colors.bgCanvasDisabled
else -> ElementTheme.colors.bgCanvasDefault
},
content = content
)
}
@Composable
private fun SupportingTextLayout(isError: Boolean, supportingText: String) {
Row(horizontalArrangement = spacedBy(4.dp)) {
if (isError) {
Icon(
imageVector = CompoundIcons.Error(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary
)
}
Text(
text = supportingText,
color = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
@Composable
private fun textFieldStyle(enabled: Boolean): TextStyle {
return ElementTheme.typography.fontBodyLgRegular.copy(
color = if (enabled) {
ElementTheme.colors.textPrimary
} else {
ElementTheme.colors.textSecondary
}
)
}
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldLightPreview() =
ElementPreviewLight { ContentToPreview() }
internal fun TextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
@ExcludeFromCoverage
@Composable
@ExcludeFromCoverage
private fun ContentToPreview() {
Column(modifier = Modifier.padding(4.dp)) {
allBooleans.forEach { isError ->
allBooleans.forEach { enabled ->
allBooleans.forEach { readonly ->
TextField(
onValueChange = {},
label = "Label",
value = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
onValueChange = {},
label = { Text(text = "label") },
supportingText = "Supporting text",
isError = isError,
enabled = enabled,
readOnly = readonly,
@ -174,62 +313,3 @@ private fun ContentToPreview() {
}
}
}
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldValueLightPreview() =
ElementPreviewLight { TextFieldValueContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldValueTextFieldDarkPreview() =
ElementPreviewDark { TextFieldValueContentToPreview() }
@ExcludeFromCoverage
@Composable
private fun TextFieldValueContentToPreview() {
Column(modifier = Modifier.padding(4.dp)) {
allBooleans.forEach { isError ->
allBooleans.forEach { enabled ->
allBooleans.forEach { readonly ->
TextField(
value = TextFieldValue(
text = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
selection = TextRange(0, "Hello".length),
),
onValueChange = {},
label = { Text(text = "label") },
isError = isError,
enabled = enabled,
readOnly = readonly,
)
Spacer(modifier = Modifier.height(2.dp))
}
}
}
}
}
@Suppress("ModifierComposed")
@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)
}
}
}
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c94e5e7c3b044017e6cb98985671875c7ab6103533e2a5e15aed17a01a52576b
size 30470

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5053e8716e3bfe5703f6d00e304101bbbf6cdebaf5eb5088a6043171a1f34e47
size 41328

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6270f3b7935c47da76ce4e81aa7e8eac13a9340e7ccb198f8cd6956d746dfa8
size 59452

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6a98ba9f5edc21b8781e84ccaf6f93c3608a4aa64464494304962d8cb62a102
size 31477

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:150b375f3fa9c180ec221b3fcc3ed0397b75920cb760e25e327fd6be46370618
size 41954

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3b02b0bc0d557bac4d5aa945daf04d5d58dab44d9e4284d030804e6b5ed6a49
size 60703

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7de44cc8f686264788aa79cde24b9896c6ca4dabc49b246abbd1d3dd0311e25
size 57891

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2df49a9c82438dbf1ba0ba0670cadd8e7bb47a7e25de27793fd756760cf7c5f2
size 79562

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2df49a9c82438dbf1ba0ba0670cadd8e7bb47a7e25de27793fd756760cf7c5f2
size 79562

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c754daf307554dc0c83ba0f89c38f5fa7f5c2edff82907b0826c2b781498dbcb
size 56047

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6e43161abdccacbede4a8c4b4ff446026035df4be0894a55c6364681458bb2f
size 78805

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6e43161abdccacbede4a8c4b4ff446026035df4be0894a55c6364681458bb2f
size 78805

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74f12ea2c5114363b809fcf4d897487cb87ecfab361952471c05d22898d0048f
size 130010
oid sha256:a9d0338485aabe5296754868ce2fcd5097d914eea28298ec11c16f7161ae2341
size 111617

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6950f5e0824c964936077ecda4ff7edb8c8c6796b5a4ae304e6027e57a83cb55
size 116382
oid sha256:88584b1d4a4876e68e2972f9bbd03156127ee9b552de1ec20a9057d8f6cb1828
size 101296

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:afa896a21369e764e32b1dd4858a34708ddf38426a4a8aaa724761e76a90fed7
size 35316
oid sha256:91cdcd831bc82480a130093a2f561bde8f0c3f0f2b9e7251628763202fc7bc39
size 35249

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2dc060e53c33a7a06895e1be02f45f7f7e52b287d87b41c76d3321a71184f525
size 36501
oid sha256:971e260530cfd5b25cce035272820cb067c7e4a4a34fa6992526e5176913ea2d
size 36617

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1cdff1f6e189eff691125330b0f2c733ffd16fca324a5f2faee3dd15c9b7bc90
size 27119
oid sha256:56fde0cdb5a5196c277c43a1b9909401f40a4e7ef8bfe2e4d80592f0768fe5d5
size 27306

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60e85a3e25fd112009111634ddc59a6ae7d884e26ea29e0598c1af10818abcb5
size 34285
oid sha256:2db3dad746069968a83693752e985c25b72ca31be11b5c65d7da234f96d377d3
size 34055

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b518aa920d7183d740be4358dcc495e7df1c2d40c866addb728c7af8e273dbe
size 35502
oid sha256:61739bd834ee18df778b4737f38f656d4342397135d518791ff4518d8fdea96d
size 35173

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a96833c5ba2508d77e1b8a1f885df78fb979fcb530223c4591076120729559e4
size 25080
oid sha256:ae8a729e91657959dbac49ef1c0f41fe37c296923eef6c22bdc9692d389ca346
size 25207

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4054b601d6afc38467b632a19a5e12540b21dccbd67a885bcc13f8ad8a86636e
size 23532
oid sha256:a5419be4719399829da9684e6635873168f274120738e43c0b101603d8499da1
size 23562

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99fae3f838aa948b77391ef87d71ef56f2ac68b734bfdbd9c2b1868793dba192
size 51583
oid sha256:cd128a94f904caabcd0557b2d7742e743fe80ad8618d96148dc469d7c0f3a006
size 51651

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2426456773a27afb8daedb3d4ce8d03901d5b4c5a92c034537a79cd0ed018b0b
size 23067
oid sha256:d27d44214d8f083ee8ecbda76e060167af26f00052c9c0acb43b77e87432ae54
size 23084

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b929faac1dc1e016ae59333941612231fdc56ceeba56515b28d7e768e7fce4e2
size 50630
oid sha256:ecd37beb7a097aadda38238b36327dc5da809834f489c583146b9189d4e8a628
size 50563

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de520c3c0cf2133c2f862e061055b24d11a9fd7b54644b7aaaa989a8ecbe466f
size 76719
oid sha256:d8a8e7035c518edf069c9860e9fe4a894d144420541d6397066ec5ee7742df57
size 77584

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ac8095520e4a80826f34733f90d1cb3d56601f194edbd890508a6fc6f31eadf
size 75246
oid sha256:2fc2fd1c67088e29a3d8e2dc633b411cd8256d821243d5d2f96bcf6b8e5ab6cc
size 75979

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9d2f18ee31a3370c08ba437ac21173538117b8fcdfeb45665677746c9fdd335
size 60790
oid sha256:cd29b3483f20da49930f1bc65c04c25a5ba045a8cf7557ca65df26be3663d94a
size 61388

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59af9b4f6e390612464e8e83a1fbc0657a4bbd8c954c160e10c05bc6d48442fc
size 55191
oid sha256:e26369765383ad4e9d22dc0c0a2945fc28ebfcdc5f8f387dcebcc3db859de544
size 55677

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d244fa646c87a6379d14eab9fa182652479473b5c77559e4631f3ffa19c0e1c4
size 52134
oid sha256:cf8e762edb7915238e9f2dc3b71b32d44659a02e031b169d004eb15b497e45c4
size 52747

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:babfe5dd3b5eaff3cddeb3a09b6cc72c86241510311bad2322d3938919dd6e01
size 74907
oid sha256:d8bcd7f9287d275ee4fe2d9c8d6ef39715644696c8ddeaf84db7eb5877cce4c7
size 75645

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cca5bcbc56dcd6b47e4608682a0a343eb612a0185332e245ca0025336a09bcd0
size 73344
oid sha256:1cc6acf5ef063b3bf4d101afe0c65435e2c4a68990ab3e860f57ad0bc18b32ec
size 74073

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:88e989abce89da86ef8a7e2a4e036ac3ff9b41e38270edc3be9959a18cd4c86c
size 57191
oid sha256:d5fdc298a5f3320d57d5709f8d979b13712351d2b29e01be88f1ad43b9bb04bf
size 57856

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9264c3aab735dc4546e9a12eb7698ad4572c911489906310044c8a5c65336a56
size 52219
oid sha256:f32a16cdf84a60cc3d74fb86201355dbcae8ec3dbb4b95c1999e0106752299f9
size 52817

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7e9d33860371dd2a280175f9482e43589aeaf92f443e3d7cb7ab487cdef1dfb
size 48737
oid sha256:2132cd35e731279fba539e10b2843b1ae012d1db7e01653e2f0ec60c054e9061
size 49369

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85690981c628723b7ae353fe82277967dfda763953d0fca91e2575e3d8d84c25
size 43788
oid sha256:24d232b144ff5664fecbd3c83fc04533eef8b3e82e4984b93f4156d996ad33e2
size 43569

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d57f594ecf21d414ed0c301b30aa400ad3d04ca4aacae647899ae7550db7521a
size 45012
oid sha256:741ad1b881c9598c42c91603e84166768b5dc5a713ec191dc1c34c05360dbb92
size 44908

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f81c06c3cbc14d8f9528a36f46ea9b39510f6eb9b1f93e731d4a182ed2a9dba8
size 44549
oid sha256:0da11f0f4d9c191218e0d45caa37620db92311bbf126788a1ac1d84ca03b63a5
size 44444

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fba92eab1bbfc9338db2581f8375c3edf09b3130e7997c1016cf0dcf782284c
size 43209
oid sha256:cc14af0383a159fbba7dc643e0d925db9ff8c4b22c2e38afcda20b504fd53d93
size 44165

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc2bdf0c222a688693e3df942b1c359f0e53799e1a81288f2a04a73bb53929c4
size 34245
oid sha256:e96f672af0dd6a622deb18bfccea861cb51b63c3355e85ee8a8db6543e3de05c
size 33803

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f81c06c3cbc14d8f9528a36f46ea9b39510f6eb9b1f93e731d4a182ed2a9dba8
size 44549
oid sha256:0da11f0f4d9c191218e0d45caa37620db92311bbf126788a1ac1d84ca03b63a5
size 44444

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2801aecec234fa032e060262f030aebbf39cd23c9d4bfa014caddd422e93ffb
size 42482
oid sha256:31e50bed6ff94d2e20bb8cb0b9c35154de4c148c56a37518b0721fe463d7078b
size 42242

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0b38c4e39ce03f0fbdcb51425872a3ed096d58b5c16d05ced11bea882053c58
size 43652
oid sha256:ca3973d21ea5575ab0921f78c4443c69c21f1f3f07ca78612c57bfe58b261002
size 43748

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a26fb7c853e0dd27b48a5d9ef247067f6bd8f8668695949be592dca1e54d3634
size 43311
oid sha256:f46c2d027a11f6dfca7edabc3d12ca7feed43c9688007c7cac9b654fb6fd6710
size 43411

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c79d8c292de4b8dab500345a737274d84580f3d8c886652988c004363f975597
size 42179
oid sha256:4b5219d31af9590df4e78041af6cf217cfadd7cbe67753b22e919758e4a31f53
size 42608

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:02826a3e3870f8c64a91a1ef0662cf2aa17818481e1c1d5f6ac3512eadb614a5
size 32041
oid sha256:85e3ccef2770f569ad2b062b9d7b86438b95e7235bf4d511621fa21dc2086f3e
size 31504

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a26fb7c853e0dd27b48a5d9ef247067f6bd8f8668695949be592dca1e54d3634
size 43311
oid sha256:f46c2d027a11f6dfca7edabc3d12ca7feed43c9688007c7cac9b654fb6fd6710
size 43411

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1dfe32bea70bff2eb009cde05441a8476701366d34341468a96101c336630c3d
size 32242
oid sha256:88fbd2415816962033bc3a5798c2e75d3e6fffe2e2886072b67f9acb8643d1e2
size 31586

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c34faf032e7f38b49d9bb7e99c8688f9e1c7fd821d951a26c52b2bb4bc9cf24d
size 36637
oid sha256:d1d455bd313f90002307e23823e14189bf53ebd0f0b1cfa4478514d2bb8ab7d0
size 36440

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5199a5d1f36ee96a30c81dd328dee7c07fc96fd5eab8a4b93f2fe33ccef4405d
size 38989
oid sha256:470d70f74c6c5560f5324874b2cedd0458614e3bdf6c65356879eb42ff337bb2
size 38892

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3153a4e0b95bc3c6cd7c1424e864c9e1aadee95eee6a66fa03e760e9cd7cb10f
size 44462
oid sha256:cc85c3e76d57bc1b46a427ecee9ca64c238c419a346a43cd52d3a6e9f4199ed4
size 43958

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85a96fa375f9c77754feb70676a7aeb23803f9688bf3de6e61ebdf39062f42f1
size 27219
oid sha256:3399b98bd9f8d3ba2175125656db8871ba14ec98f80595d7be95bf8438a33219
size 26803

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:df844f42580ca97e481656b04056ab0ae02b32c83bb6d46411c9d60040b5cf86
size 117912
oid sha256:d59fb94b1c8430065b7e40b413d250e631511acfac88cb01c24cf63e3d354cc1
size 117561

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96d2015cfd86eec443b94263da66c769df35135e8319d19bcab137a2b363d51a
size 32781
oid sha256:dd2845e9385c972347a1200ae3edb4d6a2bc866a4f4f34f07eaf9f96d0407405
size 32270

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7af8a815fbdf00bc72ee8bdadfeb2d328b28d0f5a25d845f195889af8abba25e
size 32770
oid sha256:98a74b997bf3c1e1365abf1c944453b15f1f8eb4ee48e24d91dda05c0a602b2a
size 33245

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:16d14b57d5299dba1cec83f416d6283065281ab5a50f7cb72286d7cea3d2dff5
size 31067
oid sha256:ed0cba1b78f970ab1ef5bfc277da6fcfbad8e07d3d3d796daab43948b0423f41
size 30639

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18713df330ee3b81cd015e09151e4b4a4739ddd3201a15f8d2e1286ee16b2d1e
size 35436
oid sha256:921d30810d6e1b6abfbd24bc7cef4b90916db0b41dccfd7de2298f81882fb5c7
size 35224

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fef21564c727e0937748abcff1cd9ca45a54bfafe70176d7110f5321de9e7acf
size 36436
oid sha256:a07d6cc47b853a2f51aa8250290a5d618d702a40150bbae221bee9f2588828f9
size 36271

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a218321a53ef189108aa19985bead154eb33afd4be5adf364ad0c0569bf003bf
size 43054
oid sha256:fe35cfe537168054a634faeb6c10519b2d13b694d532b152728df588c2f3649a
size 42636

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe01f18063cdaddfaa8f3d9c3395d5dba73c1726908b28e60a945d40a83c5978
size 26468
oid sha256:58a98bf2a38cb65d90f8e6181b70b673078830182d9f506598df5bda75cfdfd7
size 26089

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7dccaaf3da114e97fdaf89b000fe62d1b8235402edd3d4027e970746057f154
size 114209
oid sha256:4af7eec8cb1d89c71a9b2fe1efb28dbb956bac465e215ba7414432d7a37c272f
size 113942

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0885f4af4e3e8afd56aaeb44f517b75eb087d9f21e1095da2c8a3590715a8672
size 31669
oid sha256:b5b1e6dbf5d662f0e4255dd0328bbe969ae6d309e4c9a5dbb6977671b940d428
size 31310

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf1b3633e2a946b3a63c326cdad8ab14dfb4d1d5b41c06754924a696cde47ea5
size 30290
oid sha256:31940f629ceaec427e44e1c4576d489550e40c652da5894bcca6da3f0642c5ff
size 30708

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1c5669f63f761311a49a7d82475ccd7463bdb5051db9add2875e13514272690
size 20189
oid sha256:4362307104a54c1e65ffe79765b54ce9b11ea482f1965d6ec2a8642b023110a6
size 20108

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe8c7f7de0d56bfc60af6771fd076104687e1f123ced09dfdedc625c03431458
size 20361
oid sha256:221e75077d503dc234772e3d3352055311e08290539f104e02c8a21463f03369
size 20239

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b1a27216d9fb2e6d28109f6f414c0a06598103c69d182cdf5e926c930ac0a6b0
size 69545
oid sha256:c19217fdb11145af9a2e86bbdb9fa203ac717e8e94ca61798b4861392bb396e1
size 69416

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:97ddd11519803c8d042e593c0d231051bc9518b4fbe2f63d80792ea34319442f
size 87044
oid sha256:cf191b543c1b87e0c33965c215b842f05e0f07ab86a4fbc2b2acbd577dc4df17
size 108891

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6fd4487ae63d2116b70a31b5ee8d18135cf735f6257134afd6572b0dbe1ac5b
size 63216
oid sha256:905112d921f655c9a122261b4a1d1980ed16079611be0b20066d976780ea8c5c
size 66019

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b1a27216d9fb2e6d28109f6f414c0a06598103c69d182cdf5e926c930ac0a6b0
size 69545
oid sha256:c19217fdb11145af9a2e86bbdb9fa203ac717e8e94ca61798b4861392bb396e1
size 69416

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c2cd5fc3c6bef3f6cfc9f1d61bee2e2e41829ae3e77a3d8de31cf2168c25f61
size 52908
oid sha256:c86dd4d1ababe8df2d373aa1b47c14bf71e4744de78bff3efdab2f9fcb5034da
size 49211

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca62ad16aa94477303c61f1796d6fff8262fde99c3b31184538f6ae9234dc950
size 67807
oid sha256:1f7c6104e319a7246c04dcc4749c01e7147b412e6905e1fec70b7331632cf1a2
size 67441

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f625b162bbd3172e953676219de6ba00e0cb34f0acbcda5ff6847359e7b867e3
size 85035
oid sha256:2497fd0b7d7ae2e8da0a69e36a34c0dd7f7c8abb52b95005cd961b3c5c4b8734
size 106454

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:23f505a91ebe3438f52c65f53362fc2d5cca57a8959e5459722217541ec24fc9
size 62030
oid sha256:d84ab79382862503da8f18a54d05578a560f515f9062af5fd6c922fe830ca84a
size 63656

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca62ad16aa94477303c61f1796d6fff8262fde99c3b31184538f6ae9234dc950
size 67807
oid sha256:1f7c6104e319a7246c04dcc4749c01e7147b412e6905e1fec70b7331632cf1a2
size 67441

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bbe73e23b4217cf10f3a5d50483bcd4d1fdc956b35129e66133c1c9ee26595bd
size 49536
oid sha256:3afd2de9146b6d29dec8e6249acbf771cc602d27f5783523da844f24f24c8daf
size 45984

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e534ca2cc96a4b7a0e840eb18d153479f9e641348216fb4237c431dc8ef727d4
size 27831
oid sha256:2450f444e0e4c7a0f354ff8a38225df5b0255280924996b0d4d40b72fdf0438f
size 27737

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:31f588a58c0e79c37edbdcc72748fb1cd69e15e2313f48c5800fcf7f473d1f57
size 21509
oid sha256:2633d18f133660a2daff4ba902abe0450cf233714d43f5de8897379784f082c5
size 21375

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1e085b1745cd0445b7954af882a73b50bbd4dea74ec6def5d49562f6839c6095
size 29353
oid sha256:4dbe0286f64a876af3bc4570141e26881462d8ca17485632e2fc599ae9eed563
size 29037

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54f82d574bca5f68787bc239ecd58cfcae94f1e6dd2f760e48dd033aba784df9
size 53535
oid sha256:09f520d47af8427aa51e0a1938adab74c3433952a68c248e090a8f99454e39bf
size 53466

Some files were not shown because too many files have changed in this diff Show more