mirror of
https://github.com/bitwarden/android.git
synced 2026-03-25 15:51:22 -05:00
Adjust top app bar (#58)
This commit is contained in:
@@ -6,7 +6,6 @@ import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -18,9 +17,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -31,7 +30,6 @@ 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.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -48,7 +46,9 @@ import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.ItemListingExpandableFabAction
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.action.BitwardenSearchActionItem
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
|
||||
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
@@ -57,8 +57,6 @@ import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoBut
|
||||
import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState
|
||||
import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFabIcon
|
||||
import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFloatingActionButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.icon.BitwardenIcon
|
||||
import com.bitwarden.authenticator.ui.platform.components.model.IconData
|
||||
import com.bitwarden.authenticator.ui.platform.components.model.IconResource
|
||||
import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
@@ -155,136 +153,81 @@ fun ItemListingScreen(
|
||||
},
|
||||
)
|
||||
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.verification_codes),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = null,
|
||||
actions = {
|
||||
if (state.viewState !is ItemListingState.ViewState.NoItems) {
|
||||
BitwardenIcon(
|
||||
modifier = Modifier.clickable {
|
||||
viewModel.trySendAction(ItemListingAction.SearchClick)
|
||||
},
|
||||
iconData = IconData.Local(R.drawable.ic_search_24px),
|
||||
tint = MaterialTheme.colorScheme.surfaceTint
|
||||
when (val currentState = state.viewState) {
|
||||
is ItemListingState.ViewState.Content -> {
|
||||
ItemListingContent(
|
||||
currentState,
|
||||
scrollBehavior,
|
||||
onNavigateToSearch = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.SearchClick
|
||||
)
|
||||
}
|
||||
},
|
||||
onScanQrCodeClick = remember(viewModel) {
|
||||
{
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
onEnterSetupKeyClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick)
|
||||
}
|
||||
},
|
||||
onItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.ItemClick(it)
|
||||
)
|
||||
}
|
||||
},
|
||||
onEditItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.EditItemClick(it)
|
||||
)
|
||||
}
|
||||
},
|
||||
onDeleteItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.DeleteItemClick(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExpandableFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "AddItemButton" }
|
||||
.padding(bottom = 16.dp),
|
||||
label = R.string.add_item.asText(),
|
||||
items = listOf(
|
||||
ItemListingExpandableFabAction.ScanQrCode(
|
||||
label = R.string.scan_a_qr_code.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_camera),
|
||||
contentDescription = stringResource(id = R.string.scan_a_qr_code),
|
||||
testTag = "ScanQRCodeButton",
|
||||
),
|
||||
onScanQrCodeClick = {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
),
|
||||
ItemListingExpandableFabAction.EnterSetupKey(
|
||||
label = R.string.enter_a_setup_key.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_keyboard_24px),
|
||||
contentDescription = stringResource(id = R.string.enter_a_setup_key),
|
||||
testTag = "EnterSetupKeyButton",
|
||||
),
|
||||
onEnterSetupKeyClick = {
|
||||
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick)
|
||||
}
|
||||
)
|
||||
),
|
||||
expandableFabIcon = ExpandableFabIcon(
|
||||
iconData = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = stringResource(id = R.string.add_item),
|
||||
testTag = "AddItemButton",
|
||||
),
|
||||
iconRotation = 45f,
|
||||
),
|
||||
}
|
||||
|
||||
is ItemListingState.ViewState.Error -> {
|
||||
Text(
|
||||
text = "Error! ${currentState.message}",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.EndOverlay,
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues),
|
||||
) {
|
||||
when (val currentState = state.viewState) {
|
||||
is ItemListingState.ViewState.Content -> {
|
||||
LazyColumn {
|
||||
items(currentState.itemList) {
|
||||
VaultVerificationCodeItem(
|
||||
authCode = it.authCode,
|
||||
name = it.issuer,
|
||||
username = it.username,
|
||||
periodSeconds = it.periodSeconds,
|
||||
timeLeftSeconds = it.timeLeftSeconds,
|
||||
alertThresholdSeconds = it.alertThresholdSeconds,
|
||||
startIcon = it.startIcon,
|
||||
onItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.ItemClick(it.authCode)
|
||||
)
|
||||
}
|
||||
},
|
||||
onEditItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.EditItemClick(it.id)
|
||||
)
|
||||
}
|
||||
},
|
||||
onDeleteItemClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ItemListingAction.DeleteItemClick(it.id)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ItemListingState.ViewState.Loading,
|
||||
ItemListingState.ViewState.NoItems,
|
||||
-> {
|
||||
EmptyItemListingContent(
|
||||
appTheme = state.appTheme,
|
||||
scrollBehavior = scrollBehavior,
|
||||
onAddCodeClick = remember(viewModel) {
|
||||
{
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
onScanQuCodeClick = remember(viewModel) {
|
||||
{
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
onEnterSetupKeyClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick)
|
||||
}
|
||||
}
|
||||
|
||||
is ItemListingState.ViewState.Error -> {
|
||||
Text(
|
||||
text = "Error! ${currentState.message}",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
ItemListingState.ViewState.NoItems,
|
||||
ItemListingState.ViewState.Loading,
|
||||
-> {
|
||||
EmptyItemListingContent(
|
||||
appTheme = state.appTheme,
|
||||
onAddCodeClick = remember(viewModel) {
|
||||
{
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,63 +275,218 @@ private fun ItemListingDialogs(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ItemListingContent(
|
||||
state: ItemListingState.ViewState.Content,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
onNavigateToSearch: () -> Unit,
|
||||
onScanQrCodeClick: () -> Unit,
|
||||
onEnterSetupKeyClick: () -> Unit,
|
||||
onItemClick: (String) -> Unit,
|
||||
onEditItemClick: (String) -> Unit,
|
||||
onDeleteItemClick: (String) -> Unit,
|
||||
) {
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenMediumTopAppBar(
|
||||
title = stringResource(id = R.string.verification_codes),
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
BitwardenSearchActionItem(
|
||||
contentDescription = stringResource(id = R.string.search_codes),
|
||||
onClick = onNavigateToSearch,
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExpandableFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "AddItemButton" }
|
||||
.padding(bottom = 16.dp),
|
||||
label = R.string.add_item.asText(),
|
||||
items = listOf(
|
||||
ItemListingExpandableFabAction.ScanQrCode(
|
||||
label = R.string.scan_a_qr_code.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_camera),
|
||||
contentDescription = stringResource(id = R.string.scan_a_qr_code),
|
||||
testTag = "ScanQRCodeButton",
|
||||
),
|
||||
onScanQrCodeClick = onScanQrCodeClick,
|
||||
),
|
||||
ItemListingExpandableFabAction.EnterSetupKey(
|
||||
label = R.string.enter_a_setup_key.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_keyboard_24px),
|
||||
contentDescription = stringResource(id = R.string.enter_a_setup_key),
|
||||
testTag = "EnterSetupKeyButton",
|
||||
),
|
||||
onEnterSetupKeyClick = onEnterSetupKeyClick
|
||||
)
|
||||
),
|
||||
expandableFabIcon = ExpandableFabIcon(
|
||||
iconData = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = stringResource(id = R.string.add_item),
|
||||
testTag = "AddItemButton",
|
||||
),
|
||||
iconRotation = 45f,
|
||||
),
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.EndOverlay,
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues),
|
||||
) {
|
||||
LazyColumn {
|
||||
items(state.itemList) {
|
||||
VaultVerificationCodeItem(
|
||||
authCode = it.authCode,
|
||||
name = it.issuer,
|
||||
username = it.username,
|
||||
periodSeconds = it.periodSeconds,
|
||||
timeLeftSeconds = it.timeLeftSeconds,
|
||||
alertThresholdSeconds = it.alertThresholdSeconds,
|
||||
startIcon = it.startIcon,
|
||||
onItemClick = { onItemClick(it.authCode) },
|
||||
onEditItemClick = { onEditItemClick(it.id) },
|
||||
onDeleteItemClick = { onDeleteItemClick(it.id) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the item listing screen with no existing items.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EmptyItemListingContent(
|
||||
modifier: Modifier = Modifier,
|
||||
appTheme: AppTheme,
|
||||
onAddCodeClick: () -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
),
|
||||
onAddCodeClick: () -> Unit,
|
||||
onScanQuCodeClick: () -> Unit,
|
||||
onEnterSetupKeyClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.verification_codes),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = null,
|
||||
actions = { }
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExpandableFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "AddItemButton" }
|
||||
.padding(bottom = 16.dp),
|
||||
label = R.string.add_item.asText(),
|
||||
items = listOf(
|
||||
ItemListingExpandableFabAction.ScanQrCode(
|
||||
label = R.string.scan_a_qr_code.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_camera),
|
||||
contentDescription = stringResource(id = R.string.scan_a_qr_code),
|
||||
testTag = "ScanQRCodeButton",
|
||||
),
|
||||
onScanQrCodeClick = onScanQuCodeClick
|
||||
),
|
||||
ItemListingExpandableFabAction.EnterSetupKey(
|
||||
label = R.string.enter_a_setup_key.asText(),
|
||||
icon = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_keyboard_24px),
|
||||
contentDescription = stringResource(id = R.string.enter_a_setup_key),
|
||||
testTag = "EnterSetupKeyButton",
|
||||
),
|
||||
onEnterSetupKeyClick = onEnterSetupKeyClick,
|
||||
)
|
||||
),
|
||||
expandableFabIcon = ExpandableFabIcon(
|
||||
iconData = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = stringResource(id = R.string.add_item),
|
||||
testTag = "AddItemButton",
|
||||
),
|
||||
iconRotation = 45f,
|
||||
),
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.EndOverlay,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
painter = painterResource(
|
||||
id = when (appTheme) {
|
||||
AppTheme.DARK -> R.drawable.ic_empty_vault_dark
|
||||
AppTheme.LIGHT -> R.drawable.ic_empty_vault_light
|
||||
AppTheme.DEFAULT -> R.drawable.ic_empty_vault
|
||||
}
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
id = R.string.empty_item_list,
|
||||
),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
painter = painterResource(
|
||||
id = when (appTheme) {
|
||||
AppTheme.DARK -> R.drawable.ic_empty_vault_dark
|
||||
AppTheme.LIGHT -> R.drawable.ic_empty_vault_light
|
||||
AppTheme.DEFAULT -> R.drawable.ic_empty_vault
|
||||
}
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
id = R.string.empty_item_list,
|
||||
),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.you_dont_have_items_to_display),
|
||||
style = Typography.titleMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.you_dont_have_items_to_display),
|
||||
style = Typography.titleMedium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(id = R.string.empty_item_list_instruction),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(id = R.string.empty_item_list_instruction),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = stringResource(R.string.add_code),
|
||||
onClick = onAddCodeClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = stringResource(R.string.add_code),
|
||||
onClick = onAddCodeClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun EmptyListingContentPreview() {
|
||||
EmptyItemListingContent(
|
||||
appTheme = AppTheme.DEFAULT,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
appTheme = AppTheme.DEFAULT,
|
||||
onAddCodeClick = { },
|
||||
onScanQuCodeClick = { },
|
||||
onEnterSetupKeyClick = { },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.bitwarden.authenticator.ui.platform.components.appbar.action
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.ui.platform.components.util.rememberVectorPainter
|
||||
|
||||
/**
|
||||
* Represents the Bitwarden search action item.
|
||||
*
|
||||
* This is an [Icon] composable tailored specifically for the search functionality
|
||||
* in the Bitwarden app.
|
||||
* It presents the search icon and offers an `onClick` callback for when the icon is tapped.
|
||||
*
|
||||
* @param contentDescription A description of the UI element, used for accessibility purposes.
|
||||
* @param onClick A callback to be invoked when this action item is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenSearchActionItem(
|
||||
contentDescription: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.testTag("SearchButton"),
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = R.drawable.ic_search_24px),
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BitwardenSearchActionItem_preview() {
|
||||
BitwardenSearchActionItem(
|
||||
contentDescription = "Search",
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bitwarden.authenticator.ui.platform.components.util
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.VectorPainter
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
|
||||
/**
|
||||
* Returns a [VectorPainter] built from the given [id] to circumvent issues with painter resources
|
||||
* recomposing unnecessarily.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberVectorPainter(
|
||||
@DrawableRes id: Int,
|
||||
): VectorPainter = rememberVectorPainter(
|
||||
image = ImageVector.vectorResource(id),
|
||||
)
|
||||
Reference in New Issue
Block a user