diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/DensityExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/DensityExtensions.kt index caebf1090..a3ce332e0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/DensityExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/DensityExtensions.kt @@ -1,11 +1,14 @@ package com.x8bit.bitwarden.ui.platform.base.util import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType /** * A function for converting pixels to [Dp] within a composable function. @@ -27,3 +30,16 @@ fun IntSize.toDpSize(density: Density): DpSize = with(density) { height = height.toDp(), ) } + +/** + * Converts a [Dp] value to [TextUnit] with [TextUnitType.Sp] as its type. + * + * This allows for easier conversion between density-independent pixels (dp) and + * scalable pixels (sp) when setting text sizes in Compose. For example, a dp value + * representing a size can be directly used for text styling. + */ +@Composable +fun Dp.toUnscaledTextUnit(): TextUnit { + val scalingFactor = LocalConfiguration.current.fontScale + return TextUnit(value / scalingFactor, TextUnitType.Sp) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountActionItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountActionItem.kt new file mode 100644 index 000000000..abc52286d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenAccountActionItem.kt @@ -0,0 +1,73 @@ +package com.x8bit.bitwarden.ui.platform.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit + +/** + * Displays an icon representing a Bitwarden account with the user's initials superimposed. + * The icon is typically a colored circle with the initials centered on it. + * + * @param initials The initials of the user to be displayed on top of the icon. + * @param color The color to be applied as the tint for the icon. + * @param onClick An action to be invoked when the icon is clicked. + */ +@Composable +fun BitwardenAccountActionItem( + initials: String, + color: Color, + onClick: () -> Unit, +) { + val iconPainter = painterResource(id = R.drawable.ic_account_initials_container) + val contentDescription = stringResource(id = R.string.account) + + Box( + contentAlignment = Alignment.Center, + ) { + IconButton(onClick = onClick) { + Icon( + painter = iconPainter, + contentDescription = contentDescription, + tint = color, + ) + } + Text( + text = initials, + style = TextStyle( + fontSize = 11.dp.toUnscaledTextUnit(), + lineHeight = 13.dp.toUnscaledTextUnit(), + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.W400, + ), + color = colorResource(id = R.color.white), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun BitwardenAccountActionItem_preview() { + val mockInitials = "BW" + val mockColor = colorResource(id = R.color.primary) + + BitwardenAccountActionItem( + initials = mockInitials, + color = mockColor, + onClick = {}, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMediumTopAppBar.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMediumTopAppBar.kt index 58afd7a4d..2fa50342f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMediumTopAppBar.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMediumTopAppBar.kt @@ -1,50 +1,42 @@ package com.x8bit.bitwarden.ui.platform.components -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.material3.DropdownMenu +import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R /** - * A custom Bitwarden-themed large top app bar with an overflow menu action. + * A custom Bitwarden-themed medium top app bar with support for actions. * - * This app bar wraps around the Material 3's [LargeTopAppBar] and customizes its appearance + * This app bar wraps around Material 3's [MediumTopAppBar] and customizes its appearance * and behavior according to the app theme. - * It provides a title and an optional overflow menu, represented by a dropdown containing - * a set of menu items. + * It provides a title and an optional set of actions on the trailing side. + * These actions are arranged within a custom action row tailored to the app's design requirements. * * @param title The text to be displayed as the title of the app bar. - * @param dropdownMenuItemContent A single overflow menu in the right with contents - * defined by the [dropdownMenuItemContent]. It is strongly recommended that this content - * be a stack of [DropdownMenuItem]. * @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar * behaves in conjunction with scrolling content. + * @param actions A lambda containing the set of actions (usually icons or similar) to display + * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in + * defining the layout of the actions. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun BitwardenMediumTopAppBar( title: String, - dropdownMenuItemContent: @Composable ColumnScope.() -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior, + actions: @Composable RowScope.() -> Unit = {}, ) { - var isOverflowMenuVisible by remember { mutableStateOf(false) } - MediumTopAppBar( colors = TopAppBarDefaults.largeTopAppBarColors( scrolledContainerColor = MaterialTheme.colorScheme.surface, @@ -57,21 +49,30 @@ fun BitwardenMediumTopAppBar( color = MaterialTheme.colorScheme.onSurface, ) }, - actions = { - Box { - IconButton(onClick = { isOverflowMenuVisible = !isOverflowMenuVisible }) { + actions = actions, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +@Composable +private fun BitwardenMediumTopAppBar_preview() { + MaterialTheme { + BitwardenMediumTopAppBar( + title = "Preview Title", + scrollBehavior = TopAppBarDefaults + .exitUntilCollapsedScrollBehavior( + rememberTopAppBarState(), + ), + actions = { + IconButton(onClick = { }) { Icon( painter = painterResource(id = R.drawable.ic_more), - contentDescription = stringResource(id = R.string.more), + contentDescription = "", tint = MaterialTheme.colorScheme.onSurface, ) } - DropdownMenu( - expanded = isOverflowMenuVisible, - onDismissRequest = { isOverflowMenuVisible = false }, - content = dropdownMenuItemContent, - ) - } - }, - ) + }, + ) + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowActionItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowActionItem.kt new file mode 100644 index 000000000..2684defe3 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowActionItem.kt @@ -0,0 +1,61 @@ +package com.x8bit.bitwarden.ui.platform.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme + +/** + * Represents a composable overflow item specifically tailored for Bitwarden's UI. + * + * This composable wraps an [IconButton] with an "overflow" icon, typically used to + * indicate more actions available that are not immediately visible on the interface. + * The item is centrally aligned within a predefined [Box] of size 24.dp. + * + * @param dropdownMenuItemContent A single overflow menu in the right with contents + * defined by the [dropdownMenuItemContent]. It is strongly recommended that this content + * be a stack of [DropdownMenuItem]. + */ +@Composable +fun BitwardenOverflowActionItem( + dropdownMenuItemContent: @Composable ColumnScope.() -> Unit = {}, +) { + var isOverflowMenuVisible by remember { mutableStateOf(false) } + Box( + contentAlignment = Alignment.Center, + ) { + IconButton(onClick = { isOverflowMenuVisible = !isOverflowMenuVisible }) { + Icon( + painter = painterResource(id = R.drawable.ic_more), + contentDescription = stringResource(id = R.string.more), + tint = MaterialTheme.colorScheme.onSurface, + ) + } + DropdownMenu( + expanded = isOverflowMenuVisible, + onDismissRequest = { isOverflowMenuVisible = false }, + content = dropdownMenuItemContent, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun BitwardenOverflowActionItem_preview() { + BitwardenTheme { + BitwardenOverflowActionItem(dropdownMenuItemContent = {}) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenSearchActionItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenSearchActionItem.kt new file mode 100644 index 000000000..7580fdfc4 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenSearchActionItem.kt @@ -0,0 +1,42 @@ +package com.x8bit.bitwarden.ui.platform.components + +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.x8bit.bitwarden.R + +/** + * 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, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_search), + contentDescription = contentDescription, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun BitwardenSearchActionItem_preview() { + BitwardenSearchActionItem( + contentDescription = "Search", + onClick = {}, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt index 4c93ba95f..fedfe3490 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt @@ -49,6 +49,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.toDp import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithTwoIcons import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch @@ -110,6 +111,9 @@ fun GeneratorScreen(viewModel: GeneratorViewModel = hiltViewModel()) { BitwardenMediumTopAppBar( title = stringResource(id = R.string.generator), scrollBehavior = scrollBehavior, + actions = { + BitwardenOverflowActionItem() + }, ) }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultLoadingView.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultLoadingView.kt index 62d98ebec..b9f469f20 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultLoadingView.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultLoadingView.kt @@ -7,10 +7,10 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color /** * Loading view for the [VaultScreen]. @@ -20,7 +20,7 @@ fun VaultLoadingView(paddingValues: PaddingValues) { Column( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) .padding(paddingValues), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItemsView.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItemsView.kt index 466c2026f..37a582e3a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItemsView.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItemsView.kt @@ -1,16 +1,23 @@ package com.x8bit.bitwarden.ui.vault.feature.vault +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.x8bit.bitwarden.R @@ -25,19 +32,36 @@ fun VaultNoItemsView( Column( modifier = Modifier .fillMaxSize() - .padding(paddingValues), + .padding(paddingValues) + .background(color = MaterialTheme.colorScheme.surface), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - modifier = Modifier.padding(16.dp), + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), text = stringResource(id = R.string.no_items), + style = MaterialTheme.typography.bodyMedium, ) + + Spacer(modifier = Modifier.height(24.dp)) + Button( - modifier = Modifier.padding(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), onClick = addItemClickAction, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ), ) { - Text(text = stringResource(id = R.string.add_an_item)) + Text( + text = stringResource(id = R.string.add_an_item), + style = MaterialTheme.typography.labelLarge, + ) } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt index 9ce4ce825..0038cc1e3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt @@ -4,24 +4,32 @@ import android.widget.Toast import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandIn import androidx.compose.animation.fadeIn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntSize import androidx.hilt.navigation.compose.hiltViewModel import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect +import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem +import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar +import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem +import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem /** * The vault screen for the application. @@ -56,6 +64,8 @@ fun VaultScreen( /** * Scaffold for the [VaultScreen] */ +@Suppress("LongMethod") +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun VaultScreenScaffold( state: VaultState, @@ -66,11 +76,26 @@ private fun VaultScreenScaffold( var accountMenuVisible by rememberSaveable { mutableStateOf(false) } + val scrollBehavior = + TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) + Scaffold( topBar = { - VaultTopBar( - accountIconClickAction = { accountMenuVisible = !accountMenuVisible }, - searchIconClickAction = searchIconClickAction, + BitwardenMediumTopAppBar( + title = stringResource(id = R.string.my_vault), + scrollBehavior = scrollBehavior, + actions = { + BitwardenAccountActionItem( + initials = state.initials, + color = state.avatarColor, + onClick = { accountMenuVisible = !accountMenuVisible }, + ) + BitwardenSearchActionItem( + contentDescription = stringResource(id = R.string.search_vault), + onClick = searchIconClickAction, + ) + BitwardenOverflowActionItem() + }, ) }, floatingActionButton = { @@ -81,22 +106,23 @@ private fun VaultScreenScaffold( enter = fadeIn() + expandIn { IntSize(width = 1, height = 1) }, ) { FloatingActionButton( - containerColor = MaterialTheme.colorScheme.primary, + containerColor = MaterialTheme.colorScheme.primaryContainer, onClick = addItemClickAction, ) { Icon( - imageVector = Icons.Filled.Add, + painter = painterResource(id = R.drawable.ic_plus), contentDescription = stringResource(id = R.string.add_item), - tint = MaterialTheme.colorScheme.onPrimary, + tint = MaterialTheme.colorScheme.onPrimaryContainer, ) } } }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { paddingValues -> - when (state) { - is VaultState.Content -> VaultContentView(paddingValues = paddingValues) - is VaultState.Loading -> VaultLoadingView(paddingValues = paddingValues) - is VaultState.NoItems -> VaultNoItemsView( + when (state.viewState) { + is VaultState.ViewState.Content -> VaultContentView(paddingValues = paddingValues) + is VaultState.ViewState.Loading -> VaultLoadingView(paddingValues = paddingValues) + is VaultState.ViewState.NoItems -> VaultNoItemsView( paddingValues = paddingValues, addItemClickAction = addItemClickAction, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index 29b31a9c5..f5976eafb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.vault +import androidx.compose.ui.graphics.Color import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -13,7 +14,12 @@ import javax.inject.Inject */ @HiltViewModel class VaultViewModel @Inject constructor() : BaseViewModel( - initialState = VaultState.Loading, + // TODO retrieve this from the data layer BIT-205 + initialState = VaultState( + initials = "BW", + avatarColor = Color.Blue, + viewState = VaultState.ViewState.Loading, + ), ) { init { @@ -21,7 +27,9 @@ class VaultViewModel @Inject constructor() : BaseViewModel + currentState.copy(viewState = VaultState.ViewState.NoItems) + } } } @@ -44,23 +52,40 @@ class VaultViewModel @Inject constructor() : BaseViewModel) : VaultState() + /** + * Loading state for the [VaultScreen], signifying that the content is being processed. + */ + data object Loading : ViewState() + + /** + * Represents a state where the [VaultScreen] has no items to display. + */ + data object NoItems : ViewState() + + /** + * Content state for the [VaultScreen] showing the actual content or items. + * + * @property itemList The list of items to be displayed in the [VaultScreen]. + */ + data class Content(val itemList: List) : ViewState() + } } /** diff --git a/app/src/main/res/drawable/ic_account_initials_container.xml b/app/src/main/res/drawable/ic_account_initials_container.xml new file mode 100644 index 000000000..21f6607b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_initials_container.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 000000000..a60add71c --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/font/sf_pro.ttf b/app/src/main/res/font/sf_pro.ttf new file mode 100644 index 000000000..4f88dc135 Binary files /dev/null and b/app/src/main/res/font/sf_pro.ttf differ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt index 3990b01d8..ee74fac70 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.vault +import androidx.compose.ui.graphics.Color import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick @@ -17,7 +18,13 @@ class VaultScreenTest : BaseComposeTest() { fun `search icon click should send SearchIconClick action`() { val viewModel = mockk(relaxed = true) { every { eventFlow } returns emptyFlow() - every { stateFlow } returns MutableStateFlow(VaultState.NoItems) + every { stateFlow } returns MutableStateFlow( + VaultState( + avatarColor = Color.Blue, + initials = "BW", + viewState = VaultState.ViewState.NoItems, + ), + ) } composeTestRule.apply { setContent { @@ -34,7 +41,13 @@ class VaultScreenTest : BaseComposeTest() { fun `floating action button click should send AddItemClick action`() { val viewModel = mockk(relaxed = true) { every { eventFlow } returns emptyFlow() - every { stateFlow } returns MutableStateFlow(VaultState.NoItems) + every { stateFlow } returns MutableStateFlow( + VaultState( + avatarColor = Color.Blue, + initials = "BW", + viewState = VaultState.ViewState.NoItems, + ), + ) } composeTestRule.apply { setContent { @@ -51,8 +64,15 @@ class VaultScreenTest : BaseComposeTest() { fun `add an item button click should send AddItemClick action`() { val viewModel = mockk(relaxed = true) { every { eventFlow } returns emptyFlow() - every { stateFlow } returns MutableStateFlow(VaultState.NoItems) + every { stateFlow } returns MutableStateFlow( + VaultState( + avatarColor = Color.Blue, + initials = "BW", + viewState = VaultState.ViewState.NoItems, + ), + ) } + composeTestRule.apply { setContent { VaultScreen(