Skip to content

Commit 9c4d4a6

Browse files
committed
Lift password state
1 parent a01564c commit 9c4d4a6

File tree

3 files changed

+72
-11
lines changed

3 files changed

+72
-11
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,12 @@ class ComponentTextInputFragment : Fragment() {
318318
// Editable password that fits in one line
319319
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_6, isDarkTheme = isDarkTheme) {
320320
val state = rememberTextFieldState("Loremipsumolor")
321+
var isPasswordVisible by remember { mutableStateOf(false) }
322+
321323
DaxSecureTextField(
322324
state = state,
325+
isPasswordVisible = isPasswordVisible,
326+
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
323327
label = "Editable password that fits in one line",
324328
)
325329
}
@@ -330,8 +334,12 @@ class ComponentTextInputFragment : Fragment() {
330334
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
331335
"eiusmod tempor incididunt ut labore.",
332336
)
337+
var isPasswordVisible by remember { mutableStateOf(false) }
338+
333339
DaxSecureTextField(
334340
state = state,
341+
isPasswordVisible = isPasswordVisible,
342+
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
335343
label = "Editable password that doesn't fit in one line",
336344
)
337345
}
@@ -342,8 +350,12 @@ class ComponentTextInputFragment : Fragment() {
342350
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
343351
"eiusmod tempor incididunt ut labore.",
344352
)
353+
var isPasswordVisible by remember { mutableStateOf(false) }
354+
345355
DaxSecureTextField(
346356
state = state,
357+
isPasswordVisible = isPasswordVisible,
358+
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
347359
label = "Non-editable password",
348360
inputMode = DaxTextFieldInputMode.ReadOnly,
349361
)
@@ -355,8 +367,12 @@ class ComponentTextInputFragment : Fragment() {
355367
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
356368
"eiusmod tempor incididunt ut labore.",
357369
)
370+
var isPasswordVisible by remember { mutableStateOf(false) }
371+
358372
DaxSecureTextField(
359373
state = state,
374+
isPasswordVisible = isPasswordVisible,
375+
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
360376
label = "Non-editable password with icon",
361377
trailingIcon = {
362378
DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon(
@@ -407,8 +423,12 @@ class ComponentTextInputFragment : Fragment() {
407423
// Disabled password
408424
view.setupThemedComposeView(id = com.duckduckgo.common.ui.internal.R.id.compose_text_input_24, isDarkTheme = isDarkTheme) {
409425
val state = rememberTextFieldState("This password input is disabled")
426+
var isPasswordVisible by remember { mutableStateOf(false) }
427+
410428
DaxSecureTextField(
411429
state = state,
430+
isPasswordVisible = isPasswordVisible,
431+
onShowHidePasswordIconClick = { isPasswordVisible = !isPasswordVisible },
412432
label = "Disabled password",
413433
inputMode = DaxTextFieldInputMode.Disabled,
414434
)

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

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ 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
3937
import androidx.compose.runtime.remember
40-
import androidx.compose.runtime.setValue
4138
import androidx.compose.ui.Modifier
4239
import androidx.compose.ui.draw.alpha
4340
import androidx.compose.ui.graphics.SolidColor
@@ -64,6 +61,10 @@ import com.duckduckgo.mobile.android.R
6461
* It's a single line text field that obscures the input by default, with an option to toggle visibility.
6562
*
6663
* @param state The state of the text field that is used to read and write the text and selection.
64+
* @param isPasswordVisible Boolean flag indicating whether the password is currently visible or obscured.
65+
* You should manage this state and update it accordingly when [onShowHidePasswordIconClick] is called.
66+
* @param onShowHidePasswordIconClick Callback for when the show/hide password icon is clicked by the user.
67+
* You should update the [isPasswordVisible] state accordingly.
6768
* @param modifier Optional [Modifier] for this text field. Can be used request focus via [Modifier.focusRequester] for example.
6869
* @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.
6970
* @param inputMode Input mode for the text field, such as editable, read-only or disabled. See [DaxTextFieldInputMode] for details.
@@ -82,6 +83,8 @@ import com.duckduckgo.mobile.android.R
8283
@Composable
8384
fun DaxSecureTextField(
8485
state: TextFieldState,
86+
isPasswordVisible: Boolean,
87+
onShowHidePasswordIconClick: () -> Unit,
8588
modifier: Modifier = Modifier,
8689
label: String? = null,
8790
inputMode: DaxTextFieldInputMode = DaxTextFieldInputMode.Editable,
@@ -92,7 +95,7 @@ fun DaxSecureTextField(
9295
) {
9396
// needed by the OutlinedTextField container
9497
val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() }
95-
var isPasswordVisible by remember { mutableStateOf(false) }
98+
val daxTextFieldColors = daxTextFieldColors()
9699

97100
// combine the password visibility toggle icon with any provided trailing icon
98101
val trailingIconCombined: @Composable (() -> Unit)? = {
@@ -106,7 +109,7 @@ fun DaxSecureTextField(
106109
},
107110
),
108111
contentDescription = null,
109-
onClick = { isPasswordVisible = !isPasswordVisible },
112+
onClick = onShowHidePasswordIconClick,
110113
enabled = inputMode == DaxTextFieldInputMode.Editable || inputMode == DaxTextFieldInputMode.ReadOnly,
111114
)
112115

@@ -129,7 +132,7 @@ fun DaxSecureTextField(
129132
),
130133
),
131134
) {
132-
CompositionLocalProvider(LocalTextSelectionColors provides daxTextFieldColors().textSelectionColors) {
135+
CompositionLocalProvider(LocalTextSelectionColors provides daxTextFieldColors.textSelectionColors) {
133136
// need to use BasicSecureTextField over OutlinedSecureTextField as the latter does not support readOnly mode
134137
BasicSecureTextField(
135138
state = state,
@@ -166,8 +169,10 @@ fun DaxSecureTextField(
166169
),
167170
enabled = inputMode == DaxTextFieldInputMode.Editable || inputMode == DaxTextFieldInputMode.ReadOnly,
168171
readOnly = inputMode == DaxTextFieldInputMode.ReadOnly || inputMode == DaxTextFieldInputMode.Disabled,
169-
textStyle = DuckDuckGoTheme.typography.body1.asTextStyle,
170-
cursorBrush = SolidColor(daxTextFieldColors().cursorColor),
172+
textStyle = DuckDuckGoTheme.typography.body1.asTextStyle.copy(
173+
color = DuckDuckGoTheme.textColors.primary,
174+
),
175+
cursorBrush = SolidColor(daxTextFieldColors.cursorColor),
171176
keyboardOptions = keyboardOptions,
172177
interactionSource = internalInteractionSource,
173178
textObfuscationMode = if (isPasswordVisible) {
@@ -210,14 +215,14 @@ fun DaxSecureTextField(
210215
null
211216
},
212217
isError = !error.isNullOrBlank(),
213-
colors = daxTextFieldColors(),
218+
colors = daxTextFieldColors,
214219
contentPadding = OutlinedTextFieldDefaults.contentPadding(),
215220
container = {
216221
OutlinedTextFieldDefaults.Container(
217222
enabled = inputMode == DaxTextFieldInputMode.Editable || inputMode == DaxTextFieldInputMode.ReadOnly,
218223
isError = !error.isNullOrBlank(),
219224
interactionSource = internalInteractionSource,
220-
colors = daxTextFieldColors(),
225+
colors = daxTextFieldColors,
221226
shape = DuckDuckGoTheme.shapes.small,
222227
)
223228
},
@@ -244,18 +249,36 @@ private fun DaxSecureTextFieldEmptyPreview() {
244249
DaxSecureTextField(
245250
state = TextFieldState(),
246251
label = "Enter password",
252+
isPasswordVisible = false,
253+
onShowHidePasswordIconClick = {},
254+
)
255+
}
256+
}
257+
258+
@PreviewFontScale
259+
@PreviewLightDark
260+
@Composable
261+
private fun DaxSecureTextFieldWithPlainTextPreview() {
262+
DaxSecureTextFieldPreviewBox {
263+
DaxSecureTextField(
264+
state = TextFieldState(initialText = "SecurePassword123"),
265+
label = "Enter password",
266+
isPasswordVisible = true,
267+
onShowHidePasswordIconClick = {},
247268
)
248269
}
249270
}
250271

251272
@PreviewFontScale
252273
@PreviewLightDark
253274
@Composable
254-
private fun DaxSecureTextFieldWithTextPreview() {
275+
private fun DaxSecureTextFieldWithObscureTextPreview() {
255276
DaxSecureTextFieldPreviewBox {
256277
DaxSecureTextField(
257278
state = TextFieldState(initialText = "SecurePassword123"),
258279
label = "Enter password",
280+
isPasswordVisible = false,
281+
onShowHidePasswordIconClick = {},
259282
)
260283
}
261284
}
@@ -266,6 +289,8 @@ private fun DaxSecureTextFieldNoLabelPreview() {
266289
DaxSecureTextFieldPreviewBox {
267290
DaxSecureTextField(
268291
state = TextFieldState(initialText = "SecurePassword123"),
292+
isPasswordVisible = false,
293+
onShowHidePasswordIconClick = {},
269294
)
270295
}
271296
}
@@ -276,6 +301,8 @@ private fun DaxSecureTextFieldEditablePreview() {
276301
DaxSecureTextFieldPreviewBox {
277302
DaxSecureTextField(
278303
state = TextFieldState(initialText = "SecurePassword123"),
304+
isPasswordVisible = false,
305+
onShowHidePasswordIconClick = {},
279306
label = "Enter password",
280307
inputMode = DaxTextFieldInputMode.Editable,
281308
)
@@ -288,6 +315,8 @@ private fun DaxSecureTextFieldDisabledPreview() {
288315
DaxSecureTextFieldPreviewBox {
289316
DaxSecureTextField(
290317
state = TextFieldState(initialText = "SecurePassword123"),
318+
isPasswordVisible = false,
319+
onShowHidePasswordIconClick = {},
291320
label = "Enter password",
292321
inputMode = DaxTextFieldInputMode.Disabled,
293322
)
@@ -300,6 +329,8 @@ private fun DaxSecureTextFieldNonEditablePreview() {
300329
DaxSecureTextFieldPreviewBox {
301330
DaxSecureTextField(
302331
state = TextFieldState(initialText = "SecurePassword123"),
332+
isPasswordVisible = false,
333+
onShowHidePasswordIconClick = {},
303334
label = "Read only password",
304335
inputMode = DaxTextFieldInputMode.ReadOnly,
305336
)
@@ -312,6 +343,8 @@ private fun DaxSecureTextFieldWithErrorPreview() {
312343
DaxSecureTextFieldPreviewBox {
313344
DaxSecureTextField(
314345
state = TextFieldState(initialText = "weak"),
346+
isPasswordVisible = false,
347+
onShowHidePasswordIconClick = {},
315348
label = "Enter password",
316349
error = "Password must be at least 8 characters",
317350
)
@@ -324,6 +357,8 @@ private fun DaxSecureTextFieldWithTrailingIconPreview() {
324357
DaxSecureTextFieldPreviewBox {
325358
DaxSecureTextField(
326359
state = TextFieldState(initialText = "SecurePassword123"),
360+
isPasswordVisible = false,
361+
onShowHidePasswordIconClick = {},
327362
label = "Enter password",
328363
trailingIcon = {
329364
DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon(
@@ -342,6 +377,8 @@ private fun DaxSecureTextFieldEmptyWithTrailingIconPreview() {
342377
DaxSecureTextFieldPreviewBox {
343378
DaxSecureTextField(
344379
state = TextFieldState(),
380+
isPasswordVisible = false,
381+
onShowHidePasswordIconClick = {},
345382
label = "Enter password",
346383
trailingIcon = {
347384
DaxTextFieldTrailingIconScope.DaxTextFieldTrailingIcon(
@@ -360,6 +397,8 @@ private fun DaxSecureTextFieldErrorWithTrailingIconPreview() {
360397
DaxSecureTextFieldPreviewBox {
361398
DaxSecureTextField(
362399
state = TextFieldState(initialText = "weak"),
400+
isPasswordVisible = false,
401+
onShowHidePasswordIconClick = {},
363402
label = "Enter password",
364403
error = "Password must contain uppercase, lowercase, and numbers",
365404
trailingIcon = {

lint-rules/src/main/java/com/duckduckgo/lint/registry/DuckDuckGoIssueRegistry.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.duckduckgo.lint.strings.PlaceholderDetector.Companion.PLACEHOLDER_MIS
3838
import com.duckduckgo.lint.ui.ColorAttributeInXmlDetector.Companion.INVALID_COLOR_ATTRIBUTE
3939
import com.duckduckgo.lint.ui.DaxButtonStylingDetector.Companion.INVALID_DAX_BUTTON_PROPERTY
4040
import com.duckduckgo.lint.ui.DaxTextColorUsageDetector.Companion.INVALID_DAX_TEXT_COLOR_USAGE
41+
import com.duckduckgo.lint.ui.DaxTextFieldTrailingIconDetector.Companion.INVALID_DAX_TEXT_FIELD_TRAILING_ICON_USAGE
4142
import com.duckduckgo.lint.ui.DaxTextViewStylingDetector.Companion.INVALID_DAX_TEXT_VIEW_PROPERTY
4243
import com.duckduckgo.lint.ui.DeprecatedAndroidWidgetsUsedInXmlDetector.Companion.DEPRECATED_WIDGET_IN_XML
4344
import com.duckduckgo.lint.ui.MissingDividerDetector.Companion.MISSING_HORIZONTAL_DIVIDER
@@ -86,6 +87,7 @@ class DuckDuckGoIssueRegistry : IssueRegistry() {
8687
NO_COMPOSE_VIEW_USAGE,
8788
NO_SET_CONTENT_USAGE,
8889
INVALID_DAX_TEXT_COLOR_USAGE,
90+
INVALID_DAX_TEXT_FIELD_TRAILING_ICON_USAGE,
8991

9092
).plus(WebViewCompatApisUsageDetector.issues)
9193

0 commit comments

Comments
 (0)