mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -06:00
Simplify the BitwardenExpandableFloatingActionButton (#5989)
This commit is contained in:
parent
3a4f1d719f
commit
572d3357ee
@ -50,7 +50,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.itemlisting.model.VaultDropdownMenuAction
|
||||||
import com.bitwarden.authenticator.ui.authenticator.feature.model.SharedCodesDisplayState
|
import com.bitwarden.authenticator.ui.authenticator.feature.model.SharedCodesDisplayState
|
||||||
import com.bitwarden.authenticator.ui.authenticator.feature.model.VerificationCodeDisplayItem
|
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.BitwardenLoadingDialog
|
||||||
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
||||||
import com.bitwarden.ui.platform.components.fab.BitwardenExpandableFloatingActionButton
|
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.header.BitwardenListHeaderText
|
||||||
import com.bitwarden.ui.platform.components.icon.model.IconData
|
import com.bitwarden.ui.platform.components.icon.model.IconData
|
||||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
@ -188,25 +188,25 @@ fun ItemListingScreen(
|
|||||||
BitwardenExpandableFloatingActionButton(
|
BitwardenExpandableFloatingActionButton(
|
||||||
modifier = Modifier.testTag("AddItemButton"),
|
modifier = Modifier.testTag("AddItemButton"),
|
||||||
items = persistentListOf(
|
items = persistentListOf(
|
||||||
ItemListingExpandableFabAction.ScanQrCode(
|
ExpandableFabOption(
|
||||||
label = BitwardenString.scan_a_qr_code.asText(),
|
label = BitwardenString.scan_a_qr_code.asText(),
|
||||||
icon = IconData.Local(
|
icon = IconData.Local(
|
||||||
iconRes = BitwardenDrawable.ic_camera_small,
|
iconRes = BitwardenDrawable.ic_camera_small,
|
||||||
contentDescription = BitwardenString.scan_a_qr_code.asText(),
|
contentDescription = BitwardenString.scan_a_qr_code.asText(),
|
||||||
testTag = "ScanQRCodeButton",
|
testTag = "ScanQRCodeButton",
|
||||||
),
|
),
|
||||||
onScanQrCodeClick = remember(viewModel) {
|
onFabOptionClick = remember(viewModel) {
|
||||||
{ launcher.launch(Manifest.permission.CAMERA) }
|
{ launcher.launch(Manifest.permission.CAMERA) }
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ItemListingExpandableFabAction.EnterSetupKey(
|
ExpandableFabOption(
|
||||||
label = BitwardenString.enter_key_manually.asText(),
|
label = BitwardenString.enter_key_manually.asText(),
|
||||||
icon = IconData.Local(
|
icon = IconData.Local(
|
||||||
iconRes = BitwardenDrawable.ic_lock_encrypted_small,
|
iconRes = BitwardenDrawable.ic_lock_encrypted_small,
|
||||||
contentDescription = BitwardenString.enter_key_manually.asText(),
|
contentDescription = BitwardenString.enter_key_manually.asText(),
|
||||||
testTag = "EnterSetupKeyButton",
|
testTag = "EnterSetupKeyButton",
|
||||||
),
|
),
|
||||||
onEnterSetupKeyClick = remember(viewModel) {
|
onFabOptionClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) }
|
{ viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) }
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -17,16 +17,17 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.SmallFloatingActionButton
|
import androidx.compose.material3.SmallFloatingActionButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bitwarden.ui.platform.base.util.nullableTestTag
|
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.components.util.rememberVectorPainter
|
||||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.bitwarden.ui.util.Text
|
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 items [ExpandableFabOption] buttons displayed when the FAB is expanded.
|
||||||
* @param label [Text] displayed when the FAB is expanded.
|
* @param label [Text] displayed when the FAB is expanded.
|
||||||
* @param modifier The modifier for this composable.
|
* @param modifier The modifier for this composable.
|
||||||
* @param expandableFabState [ExpandableFabIcon] displayed in the FAB.
|
* @param initialIsExpanded The initial state of the [ExpandableFabIcon] displayed in the FAB.
|
||||||
* @param onStateChange Lambda invoked when the FAB expanded state changes.
|
*/
|
||||||
|
@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")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
|
fun BitwardenExpandableFloatingActionButton(
|
||||||
expandableFabIcon: ExpandableFabIcon,
|
expandableFabIcon: ExpandableFabIcon,
|
||||||
items: ImmutableList<T>,
|
items: ImmutableList<ExpandableFabOption>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
label: Text? = null,
|
label: Text? = null,
|
||||||
expandableFabState: MutableState<ExpandableFabState> = rememberExpandableFabState(),
|
isExpanded: Boolean,
|
||||||
onStateChange: (expandableFabState: ExpandableFabState) -> Unit = { },
|
onIsExpandedChange: (isExpanded: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.End,
|
horizontalAlignment = Alignment.End,
|
||||||
@ -58,14 +87,14 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = expandableFabState.value.isExpanded(),
|
visible = isExpanded,
|
||||||
label = "display_fab_options_animation",
|
label = "display_fab_options_animation",
|
||||||
modifier = Modifier.weight(weight = 1f),
|
modifier = Modifier.weight(weight = 1f),
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(interactionSource = null, indication = null) {
|
.clickable(interactionSource = null, indication = null) {
|
||||||
expandableFabState.value = ExpandableFabState.Collapsed
|
onIsExpandedChange(false)
|
||||||
}
|
}
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
horizontalAlignment = Alignment.End,
|
horizontalAlignment = Alignment.End,
|
||||||
@ -78,8 +107,7 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
|
|||||||
items(items) { expandableFabOption ->
|
items(items) { expandableFabOption ->
|
||||||
ExpandableFabOption(
|
ExpandableFabOption(
|
||||||
onFabOptionClick = {
|
onFabOptionClick = {
|
||||||
expandableFabState.value = expandableFabState.value.toggleValue()
|
onIsExpandedChange(!isExpanded)
|
||||||
onStateChange(expandableFabState.value)
|
|
||||||
expandableFabOption.onFabOptionClick()
|
expandableFabOption.onFabOptionClick()
|
||||||
},
|
},
|
||||||
expandableFabOption = expandableFabOption,
|
expandableFabOption = expandableFabOption,
|
||||||
@ -89,23 +117,12 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val rotation by animateFloatAsState(
|
val rotation by animateFloatAsState(
|
||||||
targetValue = if (expandableFabState.value.isExpanded()) {
|
targetValue = if (isExpanded) expandableFabIcon.iconRotation else 0f,
|
||||||
expandableFabIcon.iconRotation ?: 0f
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
},
|
|
||||||
label = "add_item_rotation",
|
label = "add_item_rotation",
|
||||||
)
|
)
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
onClick = {
|
onClick = { onIsExpandedChange(!isExpanded) },
|
||||||
expandableFabState.value = expandableFabState.value.toggleValue()
|
expanded = if (label != null) !isExpanded else false,
|
||||||
onStateChange(expandableFabState.value)
|
|
||||||
},
|
|
||||||
expanded = if (label != null) {
|
|
||||||
!expandableFabState.value.isExpanded()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
containerColor = BitwardenTheme.colorScheme.filledButton.background,
|
containerColor = BitwardenTheme.colorScheme.filledButton.background,
|
||||||
contentColor = BitwardenTheme.colorScheme.filledButton.foreground,
|
contentColor = BitwardenTheme.colorScheme.filledButton.foreground,
|
||||||
shape = BitwardenTheme.shapes.fab,
|
shape = BitwardenTheme.shapes.fab,
|
||||||
@ -131,9 +148,9 @@ fun <T : ExpandableFabOption> BitwardenExpandableFloatingActionButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun <T : ExpandableFabOption> ExpandableFabOption(
|
private fun ExpandableFabOption(
|
||||||
expandableFabOption: T,
|
expandableFabOption: ExpandableFabOption,
|
||||||
onFabOptionClick: (option: T) -> Unit,
|
onFabOptionClick: (option: ExpandableFabOption) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SmallFloatingActionButton(
|
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()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
)
|
||||||
@ -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,
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user