Skip to content

Commit f23d1ef

Browse files
committed
Update implementation
1 parent a7aa6a2 commit f23d1ef

File tree

8 files changed

+324
-87
lines changed

8 files changed

+324
-87
lines changed

android-design-system/design-system-internal/src/main/java/com/duckduckgo/common/ui/internal/ui/component/textinput/ComponentTextInputFragment.kt

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ class ComponentTextInputFragment : Fragment() {
9595
binding.outlinedinputtext31.onAction { toastOnClick(it) }
9696
binding.outlinedinputtext32.onAction { toastOnClick(it) }
9797
binding.outlinedinputtext33.onAction { toastOnClick(it) }
98+
binding.outlinedinputtext41.onAction { toastOnClick(it) }
9899
binding.outlinedinputtext21.error = "This is an error"
100+
binding.outlinedinputtext41.error = "This is an error"
99101

100102
textChangedWatcher = object : TextChangedWatcher() {
101103
override fun afterTextChanged(editable: Editable) {
@@ -318,12 +320,9 @@ class ComponentTextInputFragment : Fragment() {
318320
// Editable password that fits in one line
319321
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_6, isDarkTheme = isDarkTheme) {
320322
val state = rememberTextFieldState("Loremipsumolor")
321-
var isPasswordVisible by remember { mutableStateOf(false) }
322323

323324
DaxSecureTextField(
324325
state = state,
325-
isPasswordVisible = isPasswordVisible,
326-
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
327326
label = "Editable password that fits in one line",
328327
)
329328
}
@@ -334,12 +333,9 @@ class ComponentTextInputFragment : Fragment() {
334333
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
335334
"eiusmod tempor incididunt ut labore.",
336335
)
337-
var isPasswordVisible by remember { mutableStateOf(false) }
338336

339337
DaxSecureTextField(
340338
state = state,
341-
isPasswordVisible = isPasswordVisible,
342-
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
343339
label = "Editable password that doesn't fit in one line",
344340
)
345341
}
@@ -350,12 +346,9 @@ class ComponentTextInputFragment : Fragment() {
350346
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
351347
"eiusmod tempor incididunt ut labore.",
352348
)
353-
var isPasswordVisible by remember { mutableStateOf(false) }
354349

355350
DaxSecureTextField(
356351
state = state,
357-
isPasswordVisible = isPasswordVisible,
358-
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
359352
label = "Non-editable password",
360353
inputMode = DaxTextFieldInputMode.ReadOnly,
361354
)
@@ -367,12 +360,9 @@ class ComponentTextInputFragment : Fragment() {
367360
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
368361
"eiusmod tempor incididunt ut labore.",
369362
)
370-
var isPasswordVisible by remember { mutableStateOf(false) }
371363

372364
DaxSecureTextField(
373365
state = state,
374-
isPasswordVisible = isPasswordVisible,
375-
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
376366
label = "Non-editable password with icon",
377367
trailingIcon = {
378368
DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon(
@@ -398,6 +388,28 @@ class ComponentTextInputFragment : Fragment() {
398388
)
399389
}
400390

391+
// Non-editable text with end icon in error state
392+
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_41, isDarkTheme = isDarkTheme) {
393+
val state = rememberTextFieldState("Non-editable text with end icon in error state")
394+
395+
DaxTextField(
396+
state = state,
397+
label = "Non-editable text full click listener with end icon",
398+
error = "This is an error",
399+
trailingIcon = {
400+
DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon(
401+
painter = painterResource(R.drawable.ic_copy_24),
402+
contentDescription = "Copy",
403+
onClick = {
404+
toastOnClick(Action.PerformEndAction)
405+
},
406+
)
407+
},
408+
inputMode = DaxTextFieldInputMode.ReadOnly,
409+
lineLimits = DaxTextFieldLineLimits.SingleLine,
410+
)
411+
}
412+
401413
// Disabled text input
402414
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_22, isDarkTheme = isDarkTheme) {
403415
val state = rememberTextFieldState("This input is disabled")
@@ -423,12 +435,9 @@ class ComponentTextInputFragment : Fragment() {
423435
// Disabled password
424436
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_24, isDarkTheme = isDarkTheme) {
425437
val state = rememberTextFieldState("This password input is disabled")
426-
var isPasswordVisible by remember { mutableStateOf(false) }
427438

428439
DaxSecureTextField(
429440
state = state,
430-
isPasswordVisible = isPasswordVisible,
431-
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
432441
label = "Disabled password",
433442
inputMode = DaxTextFieldInputMode.Disabled,
434443
)

android-design-system/design-system-internal/src/main/res/layout/component_text_input_view.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,40 @@
535535
android:layout_marginTop="@dimen/keyline_1"
536536
app:platformType="view"/>
537537

538+
<com.duckduckgo.common.ui.view.text.DaxTextInput
539+
android:id="@+id/outlinedinputtext41"
540+
android:layout_width="match_parent"
541+
android:layout_height="wrap_content"
542+
android:layout_marginTop="@dimen/keyline_1"
543+
android:layout_marginBottom="@dimen/keyline_2"
544+
android:hint="Non-editable text with end icon in error state"
545+
android:text="Non-editable text with end icon in error state."
546+
app:clickable="true"
547+
app:endIcon="@drawable/ic_copy_24"
548+
app:type="single_line" />
549+
550+
<com.duckduckgo.common.ui.internal.ui.PlatformLabelView
551+
android:layout_width="match_parent"
552+
android:layout_height="wrap_content"
553+
android:layout_marginTop="@dimen/keyline_4"
554+
app:platformType="compose"/>
555+
556+
<androidx.compose.ui.platform.ComposeView
557+
android:id="@+id/compose_text_input_41"
558+
android:layout_width="match_parent"
559+
android:layout_height="wrap_content"
560+
android:layout_marginTop="@dimen/keyline_1"/>
561+
562+
<Space
563+
android:layout_width="match_parent"
564+
android:layout_height="32dp"/>
565+
566+
<com.duckduckgo.common.ui.internal.ui.PlatformLabelView
567+
android:layout_width="match_parent"
568+
android:layout_height="wrap_content"
569+
android:layout_marginTop="@dimen/keyline_1"
570+
app:platformType="view"/>
571+
538572
<com.duckduckgo.common.ui.view.text.DaxTextInput
539573
android:id="@+id/outlinedinputtext22"
540574
android:layout_width="match_parent"

android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/textfield/DaxSecureTextField.kt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import androidx.compose.material3.TextFieldLabelPosition
3434
import androidx.compose.material3.Typography
3535
import androidx.compose.runtime.Composable
3636
import androidx.compose.runtime.CompositionLocalProvider
37+
import androidx.compose.runtime.getValue
38+
import androidx.compose.runtime.mutableStateOf
3739
import androidx.compose.runtime.remember
40+
import androidx.compose.runtime.setValue
3841
import androidx.compose.ui.Modifier
3942
import androidx.compose.ui.draw.alpha
4043
import androidx.compose.ui.graphics.SolidColor
@@ -56,6 +59,55 @@ import com.duckduckgo.common.ui.compose.theme.asTextStyle
5659
import com.duckduckgo.common.ui.compose.tools.PreviewBox
5760
import com.duckduckgo.mobile.android.R
5861

62+
/**
63+
* Text field component for the DuckDuckGo design system for entering passwords and other sensitive information.
64+
* It's a single line text field that obscures the input by default, with an option to toggle visibility.
65+
*
66+
* @param state The state of the text field that is used to read and write the text and selection.
67+
* @param modifier Optional [Modifier] for this text field. Can be used request focus via [Modifier.focusRequester] for example.
68+
* @param label Optional label/hint text to display inside the text field when it's empty or above the text field when it has text or is focused.
69+
* @param inputMode Input mode for the text field, such as editable, read-only or disabled. See [DaxTextFieldInputMode] for details.
70+
* @param error Optional error message to display below the text field. If provided, the text field will be styled to indicate an error.
71+
* @param keyboardOptions Software keyboard options that contains configuration such as [KeyboardType] and [ImeAction].
72+
* See [DaxTextFieldDefaults.TextKeyboardOptions], [DaxTextFieldDefaults.IpAddressKeyboardOptions] and
73+
* [DaxTextFieldDefaults.UrlKeyboardOptions] for examples.
74+
* @param interactionSource Optional interaction source for observing and emitting interaction events.
75+
* You can use this to observe focus, pressed, hover and drag events.
76+
* @param trailingIcon Optional trailing icon composable to display at the end of the text field.
77+
* Use [DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon] to create the icon.
78+
*
79+
* Asana Task: https://app.asana.com/1/137249556945/project/1202857801505092/task/1212213756433276?focus=true
80+
* Figma reference: https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?m=auto&node-id=3202-5150
81+
*/
82+
@Composable
83+
fun DaxSecureTextField(
84+
state: TextFieldState,
85+
modifier: Modifier = Modifier,
86+
label: String? = null,
87+
inputMode: DaxTextFieldInputMode = DaxTextFieldInputMode.Editable,
88+
error: String? = null,
89+
keyboardOptions: KeyboardOptions = DaxTextFieldDefaults.PasswordKeyboardOptions,
90+
interactionSource: MutableInteractionSource? = null,
91+
trailingIcon: (@Composable DaxTextFieldTrailingIconScope.() -> Unit)? = null,
92+
) {
93+
var isPasswordVisible by remember { mutableStateOf(false) }
94+
95+
DaxSecureTextField(
96+
state = state,
97+
isPasswordVisible = isPasswordVisible,
98+
onShowHidePasswordIconClick = {
99+
isPasswordVisible = !isPasswordVisible
100+
},
101+
modifier = modifier,
102+
label = label,
103+
inputMode = inputMode,
104+
error = error,
105+
keyboardOptions = keyboardOptions,
106+
interactionSource = interactionSource,
107+
trailingIcon = trailingIcon,
108+
)
109+
}
110+
59111
/**
60112
* Text field component for the DuckDuckGo design system for entering passwords and other sensitive information.
61113
* It's a single line text field that obscures the input by default, with an option to toggle visibility.
@@ -81,7 +133,7 @@ import com.duckduckgo.mobile.android.R
81133
* Figma reference: https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?m=auto&node-id=3202-5150
82134
*/
83135
@Composable
84-
fun DaxSecureTextField(
136+
internal fun DaxSecureTextField(
85137
state: TextFieldState,
86138
isPasswordVisible: Boolean,
87139
onShowHidePasswordIconClick: () -> Unit,

android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/textfield/DaxTextField.kt

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,22 @@ fun DaxTextField(
9191
interactionSource: MutableInteractionSource? = null,
9292
trailingIcon: (@Composable DaxTextFieldTrailingIconScope.() -> Unit)? = null,
9393
) {
94+
val daxTextFieldColors = daxTextFieldColors()
95+
9496
// override is needed as TextField uses MaterialTheme.typography internally for animating the label text style
9597
MaterialTheme(
9698
typography = Typography(
9799
bodySmall = DuckDuckGoTheme.typography.caption.asTextStyle.copy(
98100
fontFamily = FontFamily.Default,
99-
color = DuckDuckGoTheme.colors.text.secondary,
101+
color = DuckDuckGoTheme.textColors.secondary,
100102
),
101103
bodyLarge = DuckDuckGoTheme.typography.body1.asTextStyle.copy(
102104
fontFamily = FontFamily.Default,
103-
color = DuckDuckGoTheme.colors.text.secondary,
105+
color = DuckDuckGoTheme.textColors.secondary,
104106
),
105107
),
106108
) {
107-
CompositionLocalProvider(LocalTextSelectionColors provides daxTextFieldColors().textSelectionColors) {
109+
CompositionLocalProvider(LocalTextSelectionColors provides daxTextFieldColors.textSelectionColors) {
108110
OutlinedTextField(
109111
state = state,
110112
modifier = modifier
@@ -163,7 +165,7 @@ fun DaxTextField(
163165
lineLimits = lineLimits.toLineLimits(),
164166
inputTransformation = inputTransformation,
165167
interactionSource = interactionSource,
166-
colors = daxTextFieldColors(),
168+
colors = daxTextFieldColors,
167169
)
168170
}
169171
}
@@ -248,6 +250,10 @@ object DaxTextFieldTrailingIconScope {
248250
)
249251
}
250252
}
253+
254+
@Composable
255+
fun SomeComposable() {
256+
}
251257
}
252258

253259
@Stable
@@ -299,10 +305,10 @@ enum class DaxTextFieldInputMode {
299305

300306
@Composable
301307
internal fun daxTextFieldColors(): TextFieldColors = OutlinedTextFieldDefaults.colors(
302-
focusedTextColor = DuckDuckGoTheme.colors.text.primary,
303-
unfocusedTextColor = DuckDuckGoTheme.colors.text.primary,
304-
disabledTextColor = DuckDuckGoTheme.colors.text.primary,
305-
errorTextColor = DuckDuckGoTheme.colors.text.primary,
308+
focusedTextColor = DuckDuckGoTheme.textColors.primary,
309+
unfocusedTextColor = DuckDuckGoTheme.textColors.primary,
310+
disabledTextColor = DuckDuckGoTheme.textColors.primary,
311+
errorTextColor = DuckDuckGoTheme.textColors.primary,
306312
focusedContainerColor = Transparent,
307313
unfocusedContainerColor = Transparent,
308314
disabledContainerColor = Transparent,
@@ -312,39 +318,39 @@ internal fun daxTextFieldColors(): TextFieldColors = OutlinedTextFieldDefaults.c
312318
disabledBorderColor = DuckDuckGoTheme.colors.textField.borders,
313319
errorBorderColor = DuckDuckGoTheme.colors.destructive,
314320
focusedLabelColor = DuckDuckGoTheme.colors.accentBlue,
315-
unfocusedLabelColor = DuckDuckGoTheme.colors.text.secondary,
316-
disabledLabelColor = DuckDuckGoTheme.colors.text.secondary,
321+
unfocusedLabelColor = DuckDuckGoTheme.textColors.secondary,
322+
disabledLabelColor = DuckDuckGoTheme.textColors.secondary,
317323
errorLabelColor = DuckDuckGoTheme.colors.destructive,
318-
focusedTrailingIconColor = DuckDuckGoTheme.colors.icons.primary,
319-
unfocusedTrailingIconColor = DuckDuckGoTheme.colors.icons.primary,
320-
disabledTrailingIconColor = DuckDuckGoTheme.colors.icons.primary,
324+
focusedTrailingIconColor = DuckDuckGoTheme.iconColors.primary,
325+
unfocusedTrailingIconColor = DuckDuckGoTheme.iconColors.primary,
326+
disabledTrailingIconColor = DuckDuckGoTheme.iconColors.primary,
321327
errorTrailingIconColor = DuckDuckGoTheme.colors.destructive,
322328
focusedSupportingTextColor = DuckDuckGoTheme.colors.destructive,
323329
unfocusedSupportingTextColor = DuckDuckGoTheme.colors.destructive,
324330
disabledSupportingTextColor = DuckDuckGoTheme.colors.destructive,
325331
errorSupportingTextColor = DuckDuckGoTheme.colors.destructive,
326332
cursorColor = DuckDuckGoTheme.colors.accentBlue,
327-
errorCursorColor = DuckDuckGoTheme.colors.destructive,
333+
errorCursorColor = DuckDuckGoTheme.colors.accentBlue,
328334
selectionColors = TextSelectionColors(
329335
handleColor = DuckDuckGoTheme.colors.accentBlue,
330336
backgroundColor = DuckDuckGoTheme.colors.accentBlue.copy(alpha = DaxTextFieldDefaults.ALPHA_DISABLED),
331337
),
332-
focusedLeadingIconColor = DuckDuckGoTheme.colors.icons.primary,
333-
unfocusedLeadingIconColor = DuckDuckGoTheme.colors.icons.primary,
334-
disabledLeadingIconColor = DuckDuckGoTheme.colors.icons.primary,
338+
focusedLeadingIconColor = DuckDuckGoTheme.iconColors.primary,
339+
unfocusedLeadingIconColor = DuckDuckGoTheme.iconColors.primary,
340+
disabledLeadingIconColor = DuckDuckGoTheme.iconColors.primary,
335341
errorLeadingIconColor = DuckDuckGoTheme.colors.destructive,
336-
focusedPrefixColor = DuckDuckGoTheme.colors.text.secondary,
337-
unfocusedPrefixColor = DuckDuckGoTheme.colors.text.secondary,
338-
disabledPrefixColor = DuckDuckGoTheme.colors.text.secondary,
339-
errorPrefixColor = DuckDuckGoTheme.colors.text.secondary,
340-
focusedSuffixColor = DuckDuckGoTheme.colors.text.secondary,
341-
unfocusedSuffixColor = DuckDuckGoTheme.colors.text.secondary,
342-
disabledSuffixColor = DuckDuckGoTheme.colors.text.secondary,
343-
errorSuffixColor = DuckDuckGoTheme.colors.text.secondary,
344-
focusedPlaceholderColor = DuckDuckGoTheme.colors.text.secondary,
345-
unfocusedPlaceholderColor = DuckDuckGoTheme.colors.text.secondary,
346-
disabledPlaceholderColor = DuckDuckGoTheme.colors.text.secondary,
347-
errorPlaceholderColor = DuckDuckGoTheme.colors.text.secondary,
342+
focusedPrefixColor = DuckDuckGoTheme.textColors.secondary,
343+
unfocusedPrefixColor = DuckDuckGoTheme.textColors.secondary,
344+
disabledPrefixColor = DuckDuckGoTheme.textColors.secondary,
345+
errorPrefixColor = DuckDuckGoTheme.textColors.secondary,
346+
focusedSuffixColor = DuckDuckGoTheme.textColors.secondary,
347+
unfocusedSuffixColor = DuckDuckGoTheme.textColors.secondary,
348+
disabledSuffixColor = DuckDuckGoTheme.textColors.secondary,
349+
errorSuffixColor = DuckDuckGoTheme.textColors.secondary,
350+
focusedPlaceholderColor = DuckDuckGoTheme.textColors.secondary,
351+
unfocusedPlaceholderColor = DuckDuckGoTheme.textColors.secondary,
352+
disabledPlaceholderColor = DuckDuckGoTheme.textColors.secondary,
353+
errorPlaceholderColor = DuckDuckGoTheme.textColors.secondary,
348354
)
349355

350356
@PreviewLightDark

0 commit comments

Comments
 (0)