Skip to content

Commit c3506c1

Browse files
PM-17841: Hide additional options behind expandable section (#4687)
1 parent 1710b56 commit c3506c1

File tree

12 files changed

+384
-583
lines changed

12 files changed

+384
-583
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.x8bit.bitwarden.ui.platform.components.header
2+
3+
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.material3.Icon
8+
import androidx.compose.material3.Text
9+
import androidx.compose.material3.minimumInteractiveComponentSize
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.clip
14+
import androidx.compose.ui.draw.rotate
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.semantics.semantics
17+
import androidx.compose.ui.unit.dp
18+
import com.x8bit.bitwarden.R
19+
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
20+
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
21+
22+
/**
23+
* Reusable header element that is clickable for expanding or collapsing content.
24+
*/
25+
@Composable
26+
fun BitwardenExpandingHeader(
27+
isExpanded: Boolean,
28+
onClick: () -> Unit,
29+
modifier: Modifier = Modifier,
30+
title: String = stringResource(id = R.string.additional_options),
31+
) {
32+
Row(
33+
modifier = modifier
34+
.clip(shape = BitwardenTheme.shapes.content)
35+
.clickable(
36+
onClickLabel = stringResource(
37+
id = if (isExpanded) R.string.options_expanded else R.string.options_collapsed,
38+
),
39+
onClick = onClick,
40+
)
41+
.minimumInteractiveComponentSize()
42+
.padding(top = 16.dp, bottom = 8.dp)
43+
.padding(horizontal = 16.dp)
44+
.semantics(mergeDescendants = true) {},
45+
verticalAlignment = Alignment.CenterVertically,
46+
) {
47+
Text(
48+
text = title,
49+
color = BitwardenTheme.colorScheme.text.interaction,
50+
style = BitwardenTheme.typography.labelLarge,
51+
modifier = Modifier.padding(end = 8.dp),
52+
)
53+
val iconRotationDegrees = animateFloatAsState(
54+
targetValue = if (isExpanded) 0f else 180f,
55+
label = "expanderIconRotationAnimation",
56+
)
57+
Icon(
58+
painter = rememberVectorPainter(id = R.drawable.ic_chevron_up_small),
59+
contentDescription = null,
60+
tint = BitwardenTheme.colorScheme.icon.secondary,
61+
modifier = Modifier.rotate(degrees = iconRotationDegrees.value),
62+
)
63+
}
64+
}

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.compose.animation.fadeIn
66
import androidx.compose.animation.fadeOut
77
import androidx.compose.animation.slideInVertically
88
import androidx.compose.animation.slideOutVertically
9-
import androidx.compose.foundation.clickable
109
import androidx.compose.foundation.layout.Column
1110
import androidx.compose.foundation.layout.Row
1211
import androidx.compose.foundation.layout.Spacer
@@ -18,9 +17,7 @@ import androidx.compose.foundation.layout.padding
1817
import androidx.compose.foundation.layout.width
1918
import androidx.compose.foundation.rememberScrollState
2019
import androidx.compose.foundation.verticalScroll
21-
import androidx.compose.material3.Icon
2220
import androidx.compose.material3.Text
23-
import androidx.compose.material3.minimumInteractiveComponentSize
2421
import androidx.compose.runtime.Composable
2522
import androidx.compose.runtime.getValue
2623
import androidx.compose.runtime.mutableStateOf
@@ -31,7 +28,6 @@ import androidx.compose.ui.Modifier
3128
import androidx.compose.ui.draw.clipToBounds
3229
import androidx.compose.ui.platform.testTag
3330
import androidx.compose.ui.res.stringResource
34-
import androidx.compose.ui.semantics.semantics
3531
import androidx.compose.ui.unit.dp
3632
import com.x8bit.bitwarden.R
3733
import com.x8bit.bitwarden.ui.platform.base.util.asText
@@ -43,11 +39,11 @@ import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
4339
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
4440
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
4541
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
42+
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
4643
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
4744
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
4845
import com.x8bit.bitwarden.ui.platform.components.stepper.BitwardenStepper
4946
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
50-
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
5147
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
5248
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
5349
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
@@ -324,43 +320,14 @@ private fun AddSendOptions(
324320
addSendHandlers: AddSendHandlers,
325321
) {
326322
var isExpanded by rememberSaveable { mutableStateOf(false) }
327-
Row(
323+
BitwardenExpandingHeader(
324+
isExpanded = isExpanded,
325+
onClick = { isExpanded = !isExpanded },
328326
modifier = Modifier
329-
.testTag("SendShowHideOptionsButton")
330-
.fillMaxWidth()
331-
.clickable(
332-
onClickLabel = if (isExpanded) {
333-
stringResource(id = R.string.options_expanded)
334-
} else {
335-
stringResource(id = R.string.options_collapsed)
336-
},
337-
onClick = { isExpanded = !isExpanded },
338-
)
339-
.minimumInteractiveComponentSize()
340-
.padding(top = 16.dp, bottom = 8.dp)
327+
.testTag(tag = "SendShowHideOptionsButton")
341328
.standardHorizontalMargin()
342-
.padding(horizontal = 16.dp)
343-
.semantics(mergeDescendants = true) {},
344-
verticalAlignment = Alignment.CenterVertically,
345-
) {
346-
Text(
347-
text = stringResource(id = R.string.additional_options),
348-
color = BitwardenTheme.colorScheme.text.interaction,
349-
style = BitwardenTheme.typography.labelLarge,
350-
modifier = Modifier.padding(end = 8.dp),
351-
)
352-
Icon(
353-
painter = rememberVectorPainter(
354-
if (isExpanded) {
355-
R.drawable.ic_chevron_up_small
356-
} else {
357-
R.drawable.ic_chevron_down_small
358-
},
359-
),
360-
contentDescription = null,
361-
tint = BitwardenTheme.colorScheme.icon.secondary,
362-
)
363-
}
329+
.fillMaxWidth(),
330+
)
364331
// Hide all content if not expanded:
365332
AnimatedVisibility(
366333
visible = isExpanded,
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package com.x8bit.bitwarden.ui.vault.feature.addedit
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.Spacer
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.foundation.layout.height
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.lazy.LazyListScope
9+
import androidx.compose.foundation.lazy.items
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.platform.testTag
12+
import androidx.compose.ui.res.stringResource
13+
import androidx.compose.ui.unit.dp
14+
import com.x8bit.bitwarden.R
15+
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
16+
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
17+
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
18+
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
19+
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
20+
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
21+
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
22+
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
23+
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
24+
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
25+
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
26+
27+
/**
28+
* The collapsable UI for additional options when adding or editing a cipher.
29+
*/
30+
@Suppress("LongMethod")
31+
fun LazyListScope.vaultAddEditAdditionalOptions(
32+
itemType: VaultAddEditState.ViewState.Content.ItemType,
33+
commonState: VaultAddEditState.ViewState.Content.Common,
34+
commonTypeHandlers: VaultAddEditCommonHandlers,
35+
isAdditionalOptionsExpanded: Boolean,
36+
onAdditionalOptionsClick: () -> Unit,
37+
) {
38+
item {
39+
BitwardenExpandingHeader(
40+
isExpanded = isAdditionalOptionsExpanded,
41+
onClick = onAdditionalOptionsClick,
42+
modifier = Modifier
43+
.standardHorizontalMargin()
44+
.fillMaxWidth(),
45+
)
46+
}
47+
48+
if (isAdditionalOptionsExpanded) {
49+
val isNotes = itemType is VaultAddEditState.ViewState.Content.ItemType.SecureNotes
50+
if (!isNotes) {
51+
item(key = "optionalNotes") {
52+
BitwardenTextField(
53+
singleLine = false,
54+
label = stringResource(id = R.string.notes),
55+
value = commonState.notes,
56+
onValueChange = commonTypeHandlers.onNotesTextChange,
57+
textFieldTestTag = "ItemNotesEntry",
58+
cardStyle = CardStyle.Full,
59+
modifier = Modifier
60+
.animateItem()
61+
.fillMaxWidth()
62+
.standardHorizontalMargin(),
63+
)
64+
}
65+
}
66+
67+
if (commonState.isUnlockWithPasswordEnabled) {
68+
item(key = "MasterPasswordRepromptToggle") {
69+
Column(
70+
modifier = Modifier
71+
.animateItem()
72+
.fillMaxWidth()
73+
.standardHorizontalMargin(),
74+
) {
75+
if (!isNotes) {
76+
Spacer(modifier = Modifier.height(height = 8.dp))
77+
}
78+
BitwardenSwitch(
79+
label = stringResource(id = R.string.password_prompt),
80+
isChecked = commonState.masterPasswordReprompt,
81+
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
82+
actions = {
83+
BitwardenStandardIconButton(
84+
vectorIconRes = R.drawable.ic_question_circle_small,
85+
contentDescription = stringResource(
86+
id = R.string.master_password_re_prompt_help,
87+
),
88+
onClick = commonTypeHandlers.onTooltipClick,
89+
contentColor = BitwardenTheme.colorScheme.icon.secondary,
90+
)
91+
},
92+
cardStyle = CardStyle.Full,
93+
modifier = Modifier
94+
.testTag(tag = "MasterPasswordRepromptToggle")
95+
.fillMaxWidth(),
96+
)
97+
}
98+
}
99+
}
100+
101+
item(key = "customFieldsHeader") {
102+
Column(
103+
modifier = Modifier
104+
.animateItem()
105+
.fillMaxWidth()
106+
.standardHorizontalMargin(),
107+
) {
108+
Spacer(modifier = Modifier.height(height = 16.dp))
109+
BitwardenListHeaderText(
110+
label = stringResource(id = R.string.custom_fields),
111+
modifier = Modifier
112+
.fillMaxWidth()
113+
.padding(horizontal = 16.dp),
114+
)
115+
}
116+
}
117+
118+
items(
119+
items = commonState.customFieldData,
120+
key = { "customField_${it.itemId}" },
121+
) { customItem ->
122+
Column(
123+
modifier = Modifier
124+
.animateItem()
125+
.fillMaxWidth()
126+
.standardHorizontalMargin(),
127+
) {
128+
Spacer(modifier = Modifier.height(height = 8.dp))
129+
VaultAddEditCustomField(
130+
customField = customItem,
131+
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
132+
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
133+
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
134+
supportedLinkedTypes = itemType.vaultLinkedFieldTypes,
135+
cardStyle = CardStyle.Full,
136+
modifier = Modifier.fillMaxWidth(),
137+
)
138+
}
139+
}
140+
141+
item(key = "addCustomFieldButton") {
142+
Column(
143+
modifier = Modifier
144+
.animateItem()
145+
.fillMaxWidth()
146+
.standardHorizontalMargin(),
147+
) {
148+
Spacer(modifier = Modifier.height(height = 16.dp))
149+
VaultAddEditCustomFieldsButton(
150+
onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick,
151+
options = persistentListOfNotNull(
152+
CustomFieldType.TEXT,
153+
CustomFieldType.HIDDEN,
154+
CustomFieldType.BOOLEAN,
155+
CustomFieldType.LINKED.takeIf {
156+
itemType.vaultLinkedFieldTypes.isNotEmpty()
157+
},
158+
),
159+
modifier = Modifier.fillMaxWidth(),
160+
)
161+
}
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)