mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 03:08:50 +03:00
PM-8522: Vault tab bar title for organization users (#3632)
This commit is contained in:
parent
58a91c15aa
commit
0e90bbb905
5 changed files with 111 additions and 9 deletions
|
@ -32,6 +32,7 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
|
@ -88,6 +89,8 @@ fun VaultUnlockedNavBarScreen(
|
|||
onNavigateToPendingRequests: () -> Unit,
|
||||
onNavigateToPasswordHistory: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
navController.apply {
|
||||
val navOptions = vaultUnlockedNavBarScreenNavOptions()
|
||||
|
@ -120,6 +123,7 @@ fun VaultUnlockedNavBarScreen(
|
|||
}
|
||||
|
||||
VaultUnlockedNavBarScaffold(
|
||||
state = state,
|
||||
navController = navController,
|
||||
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||
onNavigateToVaultEditItem = onNavigateToVaultEditItem,
|
||||
|
@ -155,6 +159,7 @@ fun VaultUnlockedNavBarScreen(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("LongMethod")
|
||||
private fun VaultUnlockedNavBarScaffold(
|
||||
state: VaultUnlockedNavBarState,
|
||||
navController: NavHostController,
|
||||
vaultTabClickedAction: () -> Unit,
|
||||
sendTabClickedAction: () -> Unit,
|
||||
|
@ -184,6 +189,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
Box {
|
||||
var appBarHeightPx by remember { mutableIntStateOf(0) }
|
||||
VaultBottomAppBar(
|
||||
state = state,
|
||||
navController = navController,
|
||||
vaultTabClickedAction = vaultTabClickedAction,
|
||||
sendTabClickedAction = sendTabClickedAction,
|
||||
|
@ -254,6 +260,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun VaultBottomAppBar(
|
||||
state: VaultUnlockedNavBarState,
|
||||
navController: NavHostController,
|
||||
vaultTabClickedAction: () -> Unit,
|
||||
sendTabClickedAction: () -> Unit,
|
||||
|
@ -266,7 +273,10 @@ private fun VaultBottomAppBar(
|
|||
modifier = modifier,
|
||||
) {
|
||||
val destinations = listOf(
|
||||
VaultUnlockedNavBarTab.Vault,
|
||||
VaultUnlockedNavBarTab.Vault(
|
||||
labelRes = state.vaultNavBarLabelRes,
|
||||
contentDescriptionRes = state.vaultNavBarContentDescriptionRes,
|
||||
),
|
||||
VaultUnlockedNavBarTab.Send,
|
||||
VaultUnlockedNavBarTab.Generator,
|
||||
VaultUnlockedNavBarTab.Settings,
|
||||
|
@ -303,7 +313,7 @@ private fun VaultBottomAppBar(
|
|||
selected = isSelected,
|
||||
onClick = {
|
||||
when (destination) {
|
||||
VaultUnlockedNavBarTab.Vault -> vaultTabClickedAction()
|
||||
is VaultUnlockedNavBarTab.Vault -> vaultTabClickedAction()
|
||||
VaultUnlockedNavBarTab.Send -> sendTabClickedAction()
|
||||
VaultUnlockedNavBarTab.Generator -> generatorTabClickedAction()
|
||||
VaultUnlockedNavBarTab.Settings -> settingsTabClickedAction()
|
||||
|
@ -396,11 +406,12 @@ private sealed class VaultUnlockedNavBarTab : Parcelable {
|
|||
* Show the Vault screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Vault : VaultUnlockedNavBarTab() {
|
||||
data class Vault(
|
||||
override val labelRes: Int,
|
||||
override val contentDescriptionRes: Int,
|
||||
) : VaultUnlockedNavBarTab() {
|
||||
override val iconResSelected get() = R.drawable.ic_vault_filled
|
||||
override val iconRes get() = R.drawable.ic_vault
|
||||
override val labelRes get() = R.string.my_vault
|
||||
override val contentDescriptionRes get() = R.string.my_vault
|
||||
override val route get() = VAULT_GRAPH_ROUTE
|
||||
override val testTag get() = "VaultTab"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -14,8 +16,21 @@ import javax.inject.Inject
|
|||
class VaultUnlockedNavBarViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
specialCircumstancesManager: SpecialCircumstanceManager,
|
||||
) : BaseViewModel<Unit, VaultUnlockedNavBarEvent, VaultUnlockedNavBarAction>(
|
||||
initialState = Unit,
|
||||
) : BaseViewModel<VaultUnlockedNavBarState, VaultUnlockedNavBarEvent, VaultUnlockedNavBarAction>(
|
||||
initialState = run {
|
||||
val hasOrganization = authRepository
|
||||
.userStateFlow
|
||||
.value
|
||||
?.activeAccount
|
||||
?.organizations
|
||||
?.isNotEmpty()
|
||||
?: false
|
||||
val vaultRes = if (hasOrganization) R.string.vaults else R.string.my_vault
|
||||
VaultUnlockedNavBarState(
|
||||
vaultNavBarLabelRes = vaultRes,
|
||||
vaultNavBarContentDescriptionRes = vaultRes,
|
||||
)
|
||||
},
|
||||
) {
|
||||
init {
|
||||
when (specialCircumstancesManager.specialCircumstance) {
|
||||
|
@ -77,6 +92,14 @@ class VaultUnlockedNavBarViewModel @Inject constructor(
|
|||
// #endregion BottomTabViewModel Action Handlers
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state for the [VaultUnlockedNavBarViewModel].
|
||||
*/
|
||||
data class VaultUnlockedNavBarState(
|
||||
@StringRes val vaultNavBarLabelRes: Int,
|
||||
@StringRes val vaultNavBarContentDescriptionRes: Int,
|
||||
)
|
||||
|
||||
/**
|
||||
* Models actions for the bottom tab of the vault unlocked portion of the app.
|
||||
*/
|
||||
|
|
|
@ -31,7 +31,7 @@ sealed class VaultFilterType : Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Only data from the user's personal vault shoudl be present.
|
||||
* Only data from the user's personal vault should be present.
|
||||
*/
|
||||
@Parcelize
|
||||
data object MyVault : VaultFilterType() {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
|
|||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.navigation.navOptions
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.FakeNavHostController
|
||||
|
@ -16,7 +17,7 @@ import org.junit.Test
|
|||
class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
private val fakeNavHostController = FakeNavHostController()
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<VaultUnlockedNavBarEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(Unit)
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
|
@ -133,4 +134,25 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vault nav bar should update according to state`() {
|
||||
composeTestRule.onNodeWithText("My vault").assertExists()
|
||||
composeTestRule.onNodeWithText("Vaults").assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.tryEmit(
|
||||
VaultUnlockedNavBarState(
|
||||
vaultNavBarLabelRes = R.string.vaults,
|
||||
vaultNavBarContentDescriptionRes = R.string.vaults,
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText("My vault").assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText("Vaults").assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = VaultUnlockedNavBarState(
|
||||
vaultNavBarLabelRes = R.string.my_vault,
|
||||
vaultNavBarContentDescriptionRes = R.string.my_vault,
|
||||
)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
|
@ -10,12 +12,15 @@ import io.mockk.just
|
|||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { updateLastActiveTime() } just runs
|
||||
}
|
||||
private val specialCircumstancesManager: SpecialCircumstanceManager = mockk {
|
||||
|
@ -78,6 +83,47 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on init with no organizations should set correct vault label res`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val expected = VaultUnlockedNavBarState(
|
||||
vaultNavBarLabelRes = R.string.my_vault,
|
||||
vaultNavBarContentDescriptionRes = R.string.my_vault,
|
||||
)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
expected,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on init with organizations should set correct vault label res`() = runTest {
|
||||
val activeUserId = "activeUserId"
|
||||
val account: UserState.Account = mockk {
|
||||
every { userId } returns activeUserId
|
||||
every { organizations } returns listOf(mockk())
|
||||
}
|
||||
mutableUserStateFlow.value = UserState(
|
||||
activeUserId = activeUserId,
|
||||
accounts = listOf(account),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val expected = VaultUnlockedNavBarState(
|
||||
vaultNavBarLabelRes = R.string.vaults,
|
||||
vaultNavBarContentDescriptionRes = R.string.vaults,
|
||||
)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
expected,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `VaultTabClick should navigate to the vault screen`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
|
Loading…
Add table
Reference in a new issue