Simplify the BitwardenExpandableFloatingActionButton (#5989)

This commit is contained in:
David Perez 2025-10-08 13:31:49 -05:00 committed by GitHub
parent 3a4f1d719f
commit 572d3357ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 77 additions and 128 deletions

View File

@ -50,7 +50,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.ItemListingExpandableFabAction
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VaultDropdownMenuAction
import com.bitwarden.authenticator.ui.authenticator.feature.model.SharedCodesDisplayState
import com.bitwarden.authenticator.ui.authenticator.feature.model.VerificationCodeDisplayItem
@ -73,7 +72,8 @@ import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.bitwarden.ui.platform.components.fab.BitwardenExpandableFloatingActionButton
import com.bitwarden.ui.platform.components.fab.ExpandableFabIcon
import com.bitwarden.ui.platform.components.fab.model.ExpandableFabIcon
import com.bitwarden.ui.platform.components.fab.model.ExpandableFabOption
import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
@ -188,25 +188,25 @@ fun ItemListingScreen(
BitwardenExpandableFloatingActionButton(
modifier = Modifier.testTag("AddItemButton"),
items = persistentListOf(
ItemListingExpandableFabAction.ScanQrCode(
ExpandableFabOption(
label = BitwardenString.scan_a_qr_code.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_camera_small,
contentDescription = BitwardenString.scan_a_qr_code.asText(),
testTag = "ScanQRCodeButton",
),
onScanQrCodeClick = remember(viewModel) {
onFabOptionClick = remember(viewModel) {
{ launcher.launch(Manifest.permission.CAMERA) }
},
),
ItemListingExpandableFabAction.EnterSetupKey(
ExpandableFabOption(
label = BitwardenString.enter_key_manually.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_lock_encrypted_small,
contentDescription = BitwardenString.enter_key_manually.asText(),
testTag = "EnterSetupKeyButton",
),
onEnterSetupKeyClick = remember(viewModel) {
onFabOptionClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) }
},
),

View File

@ -1,42 +0,0 @@
package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model
import androidx.compose.material3.ExtendedFloatingActionButton
import com.bitwarden.ui.platform.components.fab.ExpandableFabOption
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.util.Text
/**
* Models [ExpandableFabOption]s that can be triggered by the [ExtendedFloatingActionButton].
*/
sealed class ItemListingExpandableFabAction(
label: Text,
icon: IconData.Local,
onFabOptionClick: () -> Unit,
) : ExpandableFabOption(label, icon, onFabOptionClick) {
/**
* Indicates the Scan QR code button was clicked.
*/
class ScanQrCode(
label: Text,
icon: IconData.Local,
onScanQrCodeClick: () -> Unit,
) : ItemListingExpandableFabAction(
label = label,
icon = icon,
onFabOptionClick = onScanQrCodeClick,
)
/**
* Indicates the Enter Key button was clicked.
*/
class EnterSetupKey(
label: Text,
icon: IconData.Local,
onEnterSetupKeyClick: () -> Unit,
) : ItemListingExpandableFabAction(
label = label,
icon = icon,
onFabOptionClick = onEnterSetupKeyClick,
)
}

View File

@ -17,16 +17,17 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.base.util.nullableTestTag
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.fab.model.ExpandableFabIcon
import com.bitwarden.ui.platform.components.fab.model.ExpandableFabOption
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.util.Text
@ -39,18 +40,46 @@ import kotlinx.collections.immutable.ImmutableList
* @param items [ExpandableFabOption] buttons displayed when the FAB is expanded.
* @param label [Text] displayed when the FAB is expanded.
* @param modifier The modifier for this composable.
* @param expandableFabState [ExpandableFabIcon] displayed in the FAB.
* @param onStateChange Lambda invoked when the FAB expanded state changes.
* @param initialIsExpanded The initial state of the [ExpandableFabIcon] displayed in the FAB.
*/
@Composable
fun BitwardenExpandableFloatingActionButton(
expandableFabIcon: ExpandableFabIcon,
items: ImmutableList<ExpandableFabOption>,
modifier: Modifier = Modifier,
label: Text? = null,
initialIsExpanded: Boolean = false,
) {
var isExpanded by rememberSaveable { mutableStateOf(value = initialIsExpanded) }
BitwardenExpandableFloatingActionButton(
expandableFabIcon = expandableFabIcon,
items = items,
label = label,
isExpanded = isExpanded,
onIsExpandedChange = { isExpanded = it },
modifier = modifier,
)
}
/**
* A FAB that expands, when clicked, to display a collection of options that can be clicked.
*
* @param expandableFabIcon The icon to display and how to display it.
* @param items [ExpandableFabOption] buttons displayed when the FAB is expanded.
* @param label [Text] displayed when the FAB is expanded.
* @param modifier The modifier for this composable.
* @param isExpanded whether the FAB is in the expanded state.
* @param onIsExpandedChange Lambda invoked when the FAB expanded state changes.
*/
@Suppress("LongMethod")
@Composable
fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
fun BitwardenExpandableFloatingActionButton(
expandableFabIcon: ExpandableFabIcon,
items: ImmutableList<T>,
items: ImmutableList<ExpandableFabOption>,
modifier: Modifier = Modifier,
label: Text? = null,
expandableFabState: MutableState<ExpandableFabState> = rememberExpandableFabState(),
onStateChange: (expandableFabState: ExpandableFabState) -> Unit = { },
isExpanded: Boolean,
onIsExpandedChange: (isExpanded: Boolean) -> Unit,
) {
Column(
horizontalAlignment = Alignment.End,
@ -58,14 +87,14 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
modifier = modifier,
) {
AnimatedVisibility(
visible = expandableFabState.value.isExpanded(),
visible = isExpanded,
label = "display_fab_options_animation",
modifier = Modifier.weight(weight = 1f),
) {
LazyColumn(
modifier = Modifier
.clickable(interactionSource = null, indication = null) {
expandableFabState.value = ExpandableFabState.Collapsed
onIsExpandedChange(false)
}
.fillMaxSize(),
horizontalAlignment = Alignment.End,
@ -78,8 +107,7 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
items(items) { expandableFabOption ->
ExpandableFabOption(
onFabOptionClick = {
expandableFabState.value = expandableFabState.value.toggleValue()
onStateChange(expandableFabState.value)
onIsExpandedChange(!isExpanded)
expandableFabOption.onFabOptionClick()
},
expandableFabOption = expandableFabOption,
@ -89,23 +117,12 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
}
val rotation by animateFloatAsState(
targetValue = if (expandableFabState.value.isExpanded()) {
expandableFabIcon.iconRotation ?: 0f
} else {
0f
},
targetValue = if (isExpanded) expandableFabIcon.iconRotation else 0f,
label = "add_item_rotation",
)
ExtendedFloatingActionButton(
onClick = {
expandableFabState.value = expandableFabState.value.toggleValue()
onStateChange(expandableFabState.value)
},
expanded = if (label != null) {
!expandableFabState.value.isExpanded()
} else {
false
},
onClick = { onIsExpandedChange(!isExpanded) },
expanded = if (label != null) !isExpanded else false,
containerColor = BitwardenTheme.colorScheme.filledButton.background,
contentColor = BitwardenTheme.colorScheme.filledButton.foreground,
shape = BitwardenTheme.shapes.fab,
@ -131,9 +148,9 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
}
@Composable
private fun <T : ExpandableFabOption> ExpandableFabOption(
expandableFabOption: T,
onFabOptionClick: (option: T) -> Unit,
private fun ExpandableFabOption(
expandableFabOption: ExpandableFabOption,
onFabOptionClick: (option: ExpandableFabOption) -> Unit,
modifier: Modifier = Modifier,
) {
SmallFloatingActionButton(
@ -163,53 +180,3 @@ private fun <T : ExpandableFabOption> ExpandableFabOption(
}
}
}
@Composable
private fun rememberExpandableFabState(): MutableState<ExpandableFabState> =
remember { mutableStateOf(ExpandableFabState.Collapsed) }
/**
* Represents options displayed when the FAB is expanded.
*/
abstract class ExpandableFabOption(
val label: Text,
val icon: IconData.Local,
val onFabOptionClick: () -> Unit,
)
/**
* Models data for an expandable FAB icon.
*/
data class ExpandableFabIcon(
val icon: IconData.Local,
val iconRotation: Float?,
)
/**
* Models the state of the expandable FAB.
*/
sealed class ExpandableFabState {
/**
* Indicates if the FAB is expanded.
*/
fun isExpanded(): Boolean = this == Expanded
/**
* Invert the state of the FAB.
*/
fun toggleValue(): ExpandableFabState = if (isExpanded()) {
Collapsed
} else {
Expanded
}
/**
* Indicates the FAB is collapsed.
*/
data object Collapsed : ExpandableFabState()
/**
* Indicates the FAB is expanded.
*/
data object Expanded : ExpandableFabState()
}

View File

@ -0,0 +1,11 @@
package com.bitwarden.ui.platform.components.fab.model
import com.bitwarden.ui.platform.components.icon.model.IconData
/**
* Models data for an expandable FAB icon.
*/
data class ExpandableFabIcon(
val icon: IconData.Local,
val iconRotation: Float,
)

View File

@ -0,0 +1,13 @@
package com.bitwarden.ui.platform.components.fab.model
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.util.Text
/**
* Represents options displayed when the FAB is expanded.
*/
data class ExpandableFabOption(
val label: Text,
val icon: IconData.Local,
val onFabOptionClick: () -> Unit,
)