diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt
index d7a331606..f59acc1e1 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt
@@ -26,6 +26,8 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
 import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
 import com.x8bit.bitwarden.ui.platform.components.model.IconResource
 import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
 
 /**
  * The top level content UI state for the [VaultItemScreen] when viewing a Login cipher.
@@ -33,9 +35,11 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
 @Suppress("LongMethod")
 @Composable
 fun VaultItemLoginContent(
-    viewState: VaultItemState.ViewState.Content.Login,
+    commonState: VaultItemState.ViewState.Content.Common,
+    loginItemState: VaultItemState.ViewState.Content.ItemType.Login,
+    vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
+    vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
     modifier: Modifier = Modifier,
-    loginHandlers: LoginHandlers,
 ) {
     LazyColumn(
         modifier = modifier,
@@ -52,7 +56,7 @@ fun VaultItemLoginContent(
             Spacer(modifier = Modifier.height(8.dp))
             BitwardenTextField(
                 label = stringResource(id = R.string.name),
-                value = viewState.name,
+                value = commonState.name,
                 onValueChange = { },
                 readOnly = true,
                 singleLine = false,
@@ -62,12 +66,12 @@ fun VaultItemLoginContent(
             )
         }
 
-        viewState.username?.let { username ->
+        loginItemState.username?.let { username ->
             item {
                 Spacer(modifier = Modifier.height(8.dp))
                 UsernameField(
                     username = username,
-                    onCopyUsernameClick = loginHandlers.onCopyUsernameClick,
+                    onCopyUsernameClick = vaultLoginItemTypeHandlers.onCopyUsernameClick,
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp),
@@ -75,14 +79,14 @@ fun VaultItemLoginContent(
             }
         }
 
-        viewState.passwordData?.let { passwordData ->
+        loginItemState.passwordData?.let { passwordData ->
             item {
                 Spacer(modifier = Modifier.height(8.dp))
                 PasswordField(
                     passwordData = passwordData,
-                    onShowPasswordClick = loginHandlers.onShowPasswordClick,
-                    onCheckForBreachClick = loginHandlers.onCheckForBreachClick,
-                    onCopyPasswordClick = loginHandlers.onCopyPasswordClick,
+                    onShowPasswordClick = vaultLoginItemTypeHandlers.onShowPasswordClick,
+                    onCheckForBreachClick = vaultLoginItemTypeHandlers.onCheckForBreachClick,
+                    onCopyPasswordClick = vaultLoginItemTypeHandlers.onCopyPasswordClick,
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp),
@@ -93,14 +97,14 @@ fun VaultItemLoginContent(
         item {
             Spacer(modifier = Modifier.height(8.dp))
             TotpField(
-                isPremiumUser = viewState.isPremiumUser,
+                isPremiumUser = commonState.isPremiumUser,
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(horizontal = 16.dp),
             )
         }
 
-        viewState.uris.takeUnless { it.isEmpty() }?.let { uris ->
+        loginItemState.uris.takeUnless { it.isEmpty() }?.let { uris ->
             item {
                 Spacer(modifier = Modifier.height(4.dp))
                 BitwardenListHeaderText(
@@ -114,8 +118,8 @@ fun VaultItemLoginContent(
                 Spacer(modifier = Modifier.height(8.dp))
                 UriField(
                     uriData = uriData,
-                    onCopyUriClick = loginHandlers.onCopyUriClick,
-                    onLaunchUriClick = loginHandlers.onLaunchUriClick,
+                    onCopyUriClick = vaultLoginItemTypeHandlers.onCopyUriClick,
+                    onLaunchUriClick = vaultLoginItemTypeHandlers.onLaunchUriClick,
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp),
@@ -123,7 +127,7 @@ fun VaultItemLoginContent(
             }
         }
 
-        viewState.notes?.let { notes ->
+        commonState.notes?.let { notes ->
             item {
                 Spacer(modifier = Modifier.height(4.dp))
                 BitwardenListHeaderText(
@@ -142,7 +146,7 @@ fun VaultItemLoginContent(
             }
         }
 
-        viewState.customFields.takeUnless { it.isEmpty() }?.let { customFields ->
+        commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields ->
             item {
                 Spacer(modifier = Modifier.height(4.dp))
                 BitwardenListHeaderText(
@@ -156,9 +160,9 @@ fun VaultItemLoginContent(
                 Spacer(modifier = Modifier.height(8.dp))
                 CustomField(
                     customField = customField,
-                    onCopyCustomHiddenField = loginHandlers.onCopyCustomHiddenField,
-                    onCopyCustomTextField = loginHandlers.onCopyCustomTextField,
-                    onShowHiddenFieldClick = loginHandlers.onShowHiddenFieldClick,
+                    onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField,
+                    onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField,
+                    onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick,
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp),
@@ -170,14 +174,14 @@ fun VaultItemLoginContent(
             Spacer(modifier = Modifier.height(24.dp))
             UpdateText(
                 header = "${stringResource(id = R.string.date_updated)}: ",
-                text = viewState.lastUpdated,
+                text = commonState.lastUpdated,
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(horizontal = 16.dp),
             )
         }
 
-        viewState.passwordRevisionDate?.let { revisionDate ->
+        loginItemState.passwordRevisionDate?.let { revisionDate ->
             item {
                 UpdateText(
                     header = "${stringResource(id = R.string.date_password_updated)}: ",
@@ -189,11 +193,11 @@ fun VaultItemLoginContent(
             }
         }
 
-        viewState.passwordHistoryCount?.let { passwordHistoryCount ->
+        loginItemState.passwordHistoryCount?.let { passwordHistoryCount ->
             item {
                 PasswordHistoryCount(
                     passwordHistoryCount = passwordHistoryCount,
-                    onPasswordHistoryClick = loginHandlers.onPasswordHistoryClick,
+                    onPasswordHistoryClick = vaultLoginItemTypeHandlers.onPasswordHistoryClick,
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp),
@@ -208,17 +212,17 @@ fun VaultItemLoginContent(
     }
 }
 
-@Suppress("LongMethod")
+@Suppress("LongMethod", "MaxLineLength")
 @Composable
 private fun CustomField(
-    customField: VaultItemState.ViewState.Content.Custom,
+    customField: VaultItemState.ViewState.Content.Common.Custom,
     onCopyCustomHiddenField: (String) -> Unit,
     onCopyCustomTextField: (String) -> Unit,
-    onShowHiddenFieldClick: (VaultItemState.ViewState.Content.Custom.HiddenField, Boolean) -> Unit,
+    onShowHiddenFieldClick: (VaultItemState.ViewState.Content.Common.Custom.HiddenField, Boolean) -> Unit,
     modifier: Modifier = Modifier,
 ) {
     when (customField) {
-        is VaultItemState.ViewState.Content.Custom.BooleanField -> {
+        is VaultItemState.ViewState.Content.Common.Custom.BooleanField -> {
             BitwardenWideSwitch(
                 label = customField.name,
                 isChecked = customField.value,
@@ -228,7 +232,7 @@ private fun CustomField(
             )
         }
 
-        is VaultItemState.ViewState.Content.Custom.HiddenField -> {
+        is VaultItemState.ViewState.Content.Common.Custom.HiddenField -> {
             BitwardenPasswordFieldWithActions(
                 label = customField.name,
                 value = customField.value,
@@ -254,7 +258,7 @@ private fun CustomField(
             )
         }
 
-        is VaultItemState.ViewState.Content.Custom.LinkedField -> {
+        is VaultItemState.ViewState.Content.Common.Custom.LinkedField -> {
             BitwardenTextField(
                 label = customField.name,
                 value = customField.vaultLinkedFieldType.label.invoke(),
@@ -269,7 +273,7 @@ private fun CustomField(
             )
         }
 
-        is VaultItemState.ViewState.Content.Custom.TextField -> {
+        is VaultItemState.ViewState.Content.Common.Custom.TextField -> {
             BitwardenTextFieldWithActions(
                 label = customField.name,
                 value = customField.value,
@@ -310,7 +314,7 @@ private fun NotesField(
 
 @Composable
 private fun PasswordField(
-    passwordData: VaultItemState.ViewState.Content.PasswordData,
+    passwordData: VaultItemState.ViewState.Content.ItemType.Login.PasswordData,
     onShowPasswordClick: (Boolean) -> Unit,
     onCheckForBreachClick: () -> Unit,
     onCopyPasswordClick: () -> Unit,
@@ -414,7 +418,7 @@ private fun UpdateText(
 
 @Composable
 private fun UriField(
-    uriData: VaultItemState.ViewState.Content.UriData,
+    uriData: VaultItemState.ViewState.Content.ItemType.Login.UriData,
     onCopyUriClick: (String) -> Unit,
     onLaunchUriClick: (String) -> Unit,
     modifier: Modifier = Modifier,
@@ -473,72 +477,3 @@ private fun UsernameField(
         modifier = modifier,
     )
 }
-
-/**
- * A class dedicated to handling user interactions related to view login cipher UI.
- * Each lambda corresponds to a specific user action, allowing for easy delegation of
- * logic when user input is detected.
- */
-@Suppress("LongParameterList")
-class LoginHandlers(
-    val onCheckForBreachClick: () -> Unit,
-    val onCopyCustomHiddenField: (String) -> Unit,
-    val onCopyCustomTextField: (String) -> Unit,
-    val onCopyPasswordClick: () -> Unit,
-    val onCopyUriClick: (String) -> Unit,
-    val onCopyUsernameClick: () -> Unit,
-    val onLaunchUriClick: (String) -> Unit,
-    val onPasswordHistoryClick: () -> Unit,
-    val onShowHiddenFieldClick: (
-        VaultItemState.ViewState.Content.Custom.HiddenField,
-        Boolean,
-    ) -> Unit,
-    val onShowPasswordClick: (isVisible: Boolean) -> Unit,
-) {
-    companion object {
-        /**
-         * Creates the [LoginHandlers] using the [viewModel] to send the desired actions.
-         */
-        @Suppress("LongMethod")
-        fun create(
-            viewModel: VaultItemViewModel,
-        ): LoginHandlers =
-            LoginHandlers(
-                onCheckForBreachClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.CheckForBreachClick)
-                },
-                onCopyCustomHiddenField = {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyCustomHiddenFieldClick(it))
-                },
-                onCopyCustomTextField = {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyCustomTextFieldClick(it))
-                },
-                onCopyPasswordClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyPasswordClick)
-                },
-                onCopyUriClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyUriClick(it))
-                },
-                onCopyUsernameClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyUsernameClick)
-                },
-                onLaunchUriClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.LaunchClick(it))
-                },
-                onPasswordHistoryClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.PasswordHistoryClick)
-                },
-                onShowHiddenFieldClick = { customField, isVisible ->
-                    viewModel.trySendAction(
-                        VaultItemAction.Login.HiddenFieldVisibilityClicked(
-                            isVisible = isVisible,
-                            field = customField,
-                        ),
-                    )
-                },
-                onShowPasswordClick = {
-                    viewModel.trySendAction(VaultItemAction.Login.PasswordVisibilityClicked(it))
-                },
-            )
-    }
-}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
index cb31c339c..a3e03547b 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
@@ -40,6 +40,8 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
 import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
 import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
 import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
 
 /**
  * Displays the vault item screen.
@@ -82,10 +84,10 @@ fun VaultItemScreen(
     VaultItemDialogs(
         dialog = state.dialog,
         onDismissRequest = remember(viewModel) {
-            { viewModel.trySendAction(VaultItemAction.DismissDialogClick) }
+            { viewModel.trySendAction(VaultItemAction.Common.DismissDialogClick) }
         },
         onSubmitMasterPassword = remember(viewModel) {
-            { viewModel.trySendAction(VaultItemAction.MasterPasswordSubmit(it)) }
+            { viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit(it)) }
         },
     )
 
@@ -101,7 +103,7 @@ fun VaultItemScreen(
                 navigationIcon = painterResource(id = R.drawable.ic_close),
                 navigationIconContentDescription = stringResource(id = R.string.close),
                 onNavigationIconClick = remember(viewModel) {
-                    { viewModel.trySendAction(VaultItemAction.CloseClick) }
+                    { viewModel.trySendAction(VaultItemAction.Common.CloseClick) }
                 },
                 actions = {
                     BitwardenOverflowActionItem()
@@ -117,7 +119,7 @@ fun VaultItemScreen(
                 FloatingActionButton(
                     containerColor = MaterialTheme.colorScheme.primaryContainer,
                     onClick = remember(viewModel) {
-                        { viewModel.trySendAction(VaultItemAction.EditClick) }
+                        { viewModel.trySendAction(VaultItemAction.Common.EditClick) }
                     },
                     modifier = Modifier.padding(bottom = 16.dp),
                 ) {
@@ -135,11 +137,11 @@ fun VaultItemScreen(
                 .imePadding()
                 .fillMaxSize()
                 .padding(innerPadding),
-            onRefreshClick = remember(viewModel) {
-                { viewModel.trySendAction(VaultItemAction.RefreshClick) }
+            vaultCommonItemTypeHandlers = remember(viewModel) {
+                VaultCommonItemTypeHandlers.create(viewModel = viewModel)
             },
-            loginHandlers = remember(viewModel) {
-                LoginHandlers.create(viewModel)
+            vaultLoginItemTypeHandlers = remember(viewModel) {
+                VaultLoginItemTypeHandlers.create(viewModel = viewModel)
             },
         )
     }
@@ -178,23 +180,41 @@ private fun VaultItemDialogs(
 @Composable
 private fun VaultItemContent(
     viewState: VaultItemState.ViewState,
+    vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
+    vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
     modifier: Modifier = Modifier,
-    onRefreshClick: () -> Unit,
-    loginHandlers: LoginHandlers,
 ) {
     when (viewState) {
         is VaultItemState.ViewState.Error -> VaultItemError(
             errorState = viewState,
-            onRefreshClick = onRefreshClick,
+            onRefreshClick = vaultCommonItemTypeHandlers.onRefreshClick,
             modifier = modifier,
         )
 
-        is VaultItemState.ViewState.Content -> when (viewState) {
-            is VaultItemState.ViewState.Content.Login -> VaultItemLoginContent(
-                viewState = viewState,
-                modifier = modifier,
-                loginHandlers = loginHandlers,
-            )
+        is VaultItemState.ViewState.Content -> {
+            when (viewState.type) {
+                is VaultItemState.ViewState.Content.ItemType.Login -> {
+                    VaultItemLoginContent(
+                        commonState = viewState.common,
+                        loginItemState = viewState.type,
+                        modifier = modifier,
+                        vaultCommonItemTypeHandlers = vaultCommonItemTypeHandlers,
+                        vaultLoginItemTypeHandlers = vaultLoginItemTypeHandlers,
+                    )
+                }
+
+                is VaultItemState.ViewState.Content.ItemType.Card -> {
+                    // TODO UI for viewing Card BIT-513
+                }
+
+                is VaultItemState.ViewState.Content.ItemType.Identity -> {
+                    // TODO UI for viewing Identity BIT-514
+                }
+
+                is VaultItemState.ViewState.Content.ItemType.SecureNote -> {
+                    // TODO UI for viewing SecureNote BIT-515
+                }
+            }
         }
 
         VaultItemState.ViewState.Loading -> VaultItemLoading(
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
index 809191019..89cd2285e 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
@@ -47,6 +47,7 @@ class VaultItemViewModel @Inject constructor(
     ),
 ) {
 
+    //region Initialization and Overrides
     init {
         combine(
             vaultRepository.getVaultItemStateFlow(state.vaultItemId),
@@ -63,124 +64,45 @@ class VaultItemViewModel @Inject constructor(
 
     override fun handleAction(action: VaultItemAction) {
         when (action) {
-            VaultItemAction.CloseClick -> handleCloseClick()
-            VaultItemAction.DismissDialogClick -> handleDismissDialogClick()
-            VaultItemAction.EditClick -> handleEditClick()
-            is VaultItemAction.MasterPasswordSubmit -> handleMasterPasswordSubmit(action)
-            VaultItemAction.RefreshClick -> handleRefreshClick()
-            is VaultItemAction.Login -> handleLoginActions(action)
+            is VaultItemAction.ItemType.Login -> handleLoginTypeActions(action)
+            is VaultItemAction.Common -> handleCommonActions(action)
             is VaultItemAction.Internal -> handleInternalAction(action)
         }
     }
+    //endregion Initialization and Overrides
 
-    private fun handleLoginActions(action: VaultItemAction.Login) {
+    //region Common Handlers
+
+    private fun handleCommonActions(action: VaultItemAction.Common) {
         when (action) {
-            VaultItemAction.Login.CheckForBreachClick -> handleCheckForBreachClick()
-            VaultItemAction.Login.CopyPasswordClick -> handleCopyPasswordClick()
-            is VaultItemAction.Login.CopyCustomHiddenFieldClick -> {
+            is VaultItemAction.Common.CloseClick -> handleCloseClick()
+            is VaultItemAction.Common.DismissDialogClick -> handleDismissDialogClick()
+            is VaultItemAction.Common.EditClick -> handleEditClick()
+            is VaultItemAction.Common.MasterPasswordSubmit -> handleMasterPasswordSubmit(action)
+            is VaultItemAction.Common.RefreshClick -> handleRefreshClick()
+            is VaultItemAction.Common.CopyCustomHiddenFieldClick -> {
                 handleCopyCustomHiddenFieldClick(action)
             }
-
-            is VaultItemAction.Login.CopyCustomTextFieldClick -> {
+            is VaultItemAction.Common.CopyCustomTextFieldClick -> {
                 handleCopyCustomTextFieldClick(action)
             }
-
-            is VaultItemAction.Login.CopyUriClick -> handleCopyUriClick(action)
-            VaultItemAction.Login.CopyUsernameClick -> handleCopyUsernameClick()
-            is VaultItemAction.Login.LaunchClick -> handleLaunchClick(action)
-            VaultItemAction.Login.PasswordHistoryClick -> handlePasswordHistoryClick()
-            is VaultItemAction.Login.PasswordVisibilityClicked -> {
-                handlePasswordVisibilityClicked(action)
-            }
-
-            is VaultItemAction.Login.HiddenFieldVisibilityClicked -> {
+            is VaultItemAction.Common.HiddenFieldVisibilityClicked -> {
                 handleHiddenFieldVisibilityClicked(action)
             }
         }
     }
 
-    private fun handleInternalAction(action: VaultItemAction.Internal) {
-        when (action) {
-            is VaultItemAction.Internal.PasswordBreachReceive -> handlePasswordBreachReceive(action)
-            is VaultItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
-            is VaultItemAction.Internal.VerifyPasswordReceive -> handleVerifyPasswordReceive(action)
-        }
-    }
-
     private fun handleCloseClick() {
         sendEvent(VaultItemEvent.NavigateBack)
     }
 
-    private fun handleCheckForBreachClick() {
-        onLoginContent { login ->
-            val password = requireNotNull(login.passwordData?.password)
-            mutableStateFlow.update {
-                it.copy(dialog = VaultItemState.DialogState.Loading)
-            }
-            viewModelScope.launch {
-                val result = authRepository.getPasswordBreachCount(password = password)
-                sendAction(VaultItemAction.Internal.PasswordBreachReceive(result))
-            }
-        }
-    }
-
-    private fun handleCopyPasswordClick() {
-        onLoginContent { login ->
-            val password = requireNotNull(login.passwordData?.password)
-            if (login.requiresReprompt) {
-                mutableStateFlow.update {
-                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
-                }
-                return@onLoginContent
-            }
-            sendEvent(VaultItemEvent.CopyToClipboard(password.asText()))
-        }
-    }
-
-    private fun handleCopyCustomHiddenFieldClick(
-        action: VaultItemAction.Login.CopyCustomHiddenFieldClick,
-    ) {
-        onContent { content ->
-            if (content.requiresReprompt) {
-                mutableStateFlow.update {
-                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
-                }
-                return@onContent
-            }
-            sendEvent(VaultItemEvent.CopyToClipboard(action.field.asText()))
-        }
-    }
-
-    private fun handleCopyCustomTextFieldClick(
-        action: VaultItemAction.Login.CopyCustomTextFieldClick,
-    ) {
-        sendEvent(VaultItemEvent.CopyToClipboard(action.field.asText()))
-    }
-
-    private fun handleCopyUriClick(action: VaultItemAction.Login.CopyUriClick) {
-        sendEvent(VaultItemEvent.CopyToClipboard(action.uri.asText()))
-    }
-
-    private fun handleCopyUsernameClick() {
-        onLoginContent { login ->
-            val username = requireNotNull(login.username)
-            if (login.requiresReprompt) {
-                mutableStateFlow.update {
-                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
-                }
-                return@onLoginContent
-            }
-            sendEvent(VaultItemEvent.CopyToClipboard(username.asText()))
-        }
-    }
-
     private fun handleDismissDialogClick() {
         mutableStateFlow.update { it.copy(dialog = null) }
     }
 
     private fun handleEditClick() {
         onContent { content ->
-            if (content.requiresReprompt) {
+            if (content.common.requiresReprompt) {
                 mutableStateFlow.update {
                     it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
                 }
@@ -190,11 +112,7 @@ class VaultItemViewModel @Inject constructor(
         }
     }
 
-    private fun handleLaunchClick(action: VaultItemAction.Login.LaunchClick) {
-        sendEvent(VaultItemEvent.NavigateToUri(action.uri))
-    }
-
-    private fun handleMasterPasswordSubmit(action: VaultItemAction.MasterPasswordSubmit) {
+    private fun handleMasterPasswordSubmit(action: VaultItemAction.Common.MasterPasswordSubmit) {
         mutableStateFlow.update {
             it.copy(dialog = VaultItemState.DialogState.Loading)
         }
@@ -213,9 +131,147 @@ class VaultItemViewModel @Inject constructor(
         }
     }
 
+    private fun handleRefreshClick() {
+        // No need to update the view state, the vault repo will emit a new state during this time
+        vaultRepository.sync()
+    }
+
+    private fun handleCopyCustomHiddenFieldClick(
+        action: VaultItemAction.Common.CopyCustomHiddenFieldClick,
+    ) {
+        onContent { content ->
+            if (content.common.requiresReprompt) {
+                mutableStateFlow.update {
+                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
+                }
+                return@onContent
+            }
+            sendEvent(VaultItemEvent.CopyToClipboard(action.field.asText()))
+        }
+    }
+
+    private fun handleCopyCustomTextFieldClick(
+        action: VaultItemAction.Common.CopyCustomTextFieldClick,
+    ) {
+        sendEvent(VaultItemEvent.CopyToClipboard(action.field.asText()))
+    }
+
+    private fun handleHiddenFieldVisibilityClicked(
+        action: VaultItemAction.Common.HiddenFieldVisibilityClicked,
+    ) {
+        onContent { content ->
+            if (content.common.requiresReprompt) {
+                mutableStateFlow.update {
+                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
+                }
+                return@onContent
+            }
+            mutableStateFlow.update { currentState ->
+                currentState.copy(
+                    viewState = content.copy(
+                        common = content.common.copy(
+                            customFields = content.common.customFields.map { customField ->
+                                if (customField == action.field) {
+                                    action.field.copy(isVisible = action.isVisible)
+                                } else {
+                                    customField
+                                }
+                            },
+                        ),
+                    ),
+                )
+            }
+        }
+    }
+
+    //endregion Common Handlers
+
+    //region Login Type Handlers
+
+    private fun handleLoginTypeActions(action: VaultItemAction.ItemType.Login) {
+        when (action) {
+            is VaultItemAction.ItemType.Login.CheckForBreachClick -> {
+                handleCheckForBreachClick()
+            }
+
+            is VaultItemAction.ItemType.Login.CopyPasswordClick -> {
+                handleCopyPasswordClick()
+            }
+
+            is VaultItemAction.ItemType.Login.CopyUriClick -> {
+                handleCopyUriClick(action)
+            }
+
+            is VaultItemAction.ItemType.Login.CopyUsernameClick -> {
+                handleCopyUsernameClick()
+            }
+
+            is VaultItemAction.ItemType.Login.LaunchClick -> {
+                handleLaunchClick(action)
+            }
+
+            is VaultItemAction.ItemType.Login.PasswordHistoryClick -> {
+                handlePasswordHistoryClick()
+            }
+
+            is VaultItemAction.ItemType.Login.PasswordVisibilityClicked -> {
+                handlePasswordVisibilityClicked(action)
+            }
+        }
+    }
+
+    private fun handleCheckForBreachClick() {
+        onLoginContent { _, login ->
+            val password = requireNotNull(login.passwordData?.password)
+            mutableStateFlow.update {
+                it.copy(dialog = VaultItemState.DialogState.Loading)
+            }
+            viewModelScope.launch {
+                val result = authRepository.getPasswordBreachCount(password = password)
+                sendAction(VaultItemAction.Internal.PasswordBreachReceive(result))
+            }
+        }
+    }
+
+    private fun handleCopyPasswordClick() {
+        onLoginContent { content, login ->
+            if (content.common.requiresReprompt) {
+                mutableStateFlow.update {
+                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
+                }
+                return@onLoginContent
+            }
+            val password = requireNotNull(login.passwordData?.password)
+            sendEvent(VaultItemEvent.CopyToClipboard(password.asText()))
+        }
+    }
+
+    private fun handleCopyUriClick(action: VaultItemAction.ItemType.Login.CopyUriClick) {
+        sendEvent(VaultItemEvent.CopyToClipboard(action.uri.asText()))
+    }
+
+    private fun handleCopyUsernameClick() {
+        onLoginContent { content, login ->
+            if (content.common.requiresReprompt) {
+                mutableStateFlow.update {
+                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
+                }
+                return@onLoginContent
+            }
+            val username = requireNotNull(login.username)
+            sendEvent(VaultItemEvent.CopyToClipboard(username.asText()))
+        }
+    }
+
+    private fun handleLaunchClick(
+        action: VaultItemAction.ItemType.Login.LaunchClick,
+    ) {
+        sendEvent(VaultItemEvent.NavigateToUri(action.uri))
+    }
+
     private fun handlePasswordHistoryClick() {
         onContent { content ->
-            if (content.requiresReprompt) {
+            if (content.common.requiresReprompt) {
                 mutableStateFlow.update {
                     it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
                 }
@@ -225,57 +281,39 @@ class VaultItemViewModel @Inject constructor(
         }
     }
 
-    private fun handleRefreshClick() {
-        // No need to update the view state, the vault repo will emit a new state during this time
-        vaultRepository.sync()
-    }
-
     private fun handlePasswordVisibilityClicked(
-        action: VaultItemAction.Login.PasswordVisibilityClicked,
+        action: VaultItemAction.ItemType.Login.PasswordVisibilityClicked,
     ) {
-        onLoginContent { login ->
-            if (login.requiresReprompt) {
+        onLoginContent { content, login ->
+            if (content.common.requiresReprompt) {
                 mutableStateFlow.update {
                     it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
                 }
                 return@onLoginContent
             }
-            mutableStateFlow.update {
-                it.copy(
-                    viewState = login.copy(
-                        passwordData = login.passwordData?.copy(
-                            isVisible = action.isVisible,
+                mutableStateFlow.update { currentState ->
+                    currentState.copy(
+                        viewState = content.copy(
+                            type = login.copy(
+                                passwordData = login.passwordData?.copy(
+                                    isVisible = action.isVisible,
+                                ),
+                            ),
                         ),
-                    ),
-                )
-            }
+                    )
+                }
         }
     }
 
-    private fun handleHiddenFieldVisibilityClicked(
-        action: VaultItemAction.Login.HiddenFieldVisibilityClicked,
-    ) {
-        onLoginContent { login ->
-            if (login.requiresReprompt) {
-                mutableStateFlow.update {
-                    it.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog)
-                }
-                return@onLoginContent
-            }
+    //endregion Login Type Handlers
 
-            mutableStateFlow.update {
-                it.copy(
-                    viewState = login.copy(
-                        customFields = login.customFields.map { customField ->
-                            if (customField == action.field) {
-                                action.field.copy(isVisible = action.isVisible)
-                            } else {
-                                customField
-                            }
-                        },
-                    ),
-                )
-            }
+    //region Internal Type Handlers
+
+    private fun handleInternalAction(action: VaultItemAction.Internal) {
+        when (action) {
+            is VaultItemAction.Internal.PasswordBreachReceive -> handlePasswordBreachReceive(action)
+            is VaultItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
+            is VaultItemAction.Internal.VerifyPasswordReceive -> handleVerifyPasswordReceive(action)
         }
     }
 
@@ -372,24 +410,24 @@ class VaultItemViewModel @Inject constructor(
             }
 
             is VerifyPasswordResult.Success -> {
-                mutableStateFlow.update {
-                    it.copy(
-                        dialog = null,
-                        viewState = when (val viewState = state.viewState) {
-                            is VaultItemState.ViewState.Content.Login -> viewState.copy(
-                                requiresReprompt = !result.isVerified,
-                            )
-
-                            is VaultItemState.ViewState.Error -> viewState
-
-                            VaultItemState.ViewState.Loading -> viewState
-                        },
-                    )
+                onContent { content ->
+                    mutableStateFlow.update {
+                        it.copy(
+                            dialog = null,
+                            viewState = content.copy(
+                                common = content.common.copy(
+                                    requiresReprompt = !result.isVerified,
+                                ),
+                            ),
+                        )
+                    }
                 }
             }
         }
     }
 
+    //endregion Internal Type Handlers
+
     private inline fun onContent(
         crossinline block: (VaultItemState.ViewState.Content) -> Unit,
     ) {
@@ -397,9 +435,18 @@ class VaultItemViewModel @Inject constructor(
     }
 
     private inline fun onLoginContent(
-        crossinline block: (VaultItemState.ViewState.Content.Login) -> Unit,
+        crossinline block: (
+            VaultItemState.ViewState.Content,
+            VaultItemState.ViewState.Content.ItemType.Login,
+        ) -> Unit,
     ) {
-        (state.viewState as? VaultItemState.ViewState.Content.Login)?.let(block)
+        (state.viewState as? VaultItemState.ViewState.Content)
+            ?.let { content ->
+                (content.type as? VaultItemState.ViewState.Content.ItemType.Login)
+                    ?.let { loginContent ->
+                        block(content, loginContent)
+                    }
+            }
     }
 }
 
@@ -434,126 +481,142 @@ data class VaultItemState(
         /**
          * Represents a loaded content state for the [VaultItemScreen].
          */
-        sealed class Content : ViewState() {
+        @Parcelize
+        data class Content(
+            val common: Common,
+            val type: ItemType,
+        ) : ViewState() {
 
             /**
-             * The name of the cipher.
-             */
-            abstract val name: String
-
-            /**
-             * A formatted date string indicating when the cipher was last updated.
-             */
-            abstract val lastUpdated: String
-
-            /**
-             * An integer indicating how many times the password has been changed.
-             */
-            abstract val passwordHistoryCount: Int?
-
-            /**
-             * Contains general notes taken by the user.
-             */
-            abstract val notes: String?
-
-            /**
-             * Indicates if the user has subscribed to a premium account or not.
-             */
-            abstract val isPremiumUser: Boolean
-
-            /**
-             * A list of custom fields that user has added.
-             */
-            abstract val customFields: List<Custom>
-
-            /**
-             * Indicates if a master password prompt is required to view secure fields.
-             */
-            abstract val requiresReprompt: Boolean
-
-            /**
-             * Represents a loaded content state for the [VaultItemScreen] when displaying a
-             * login cipher.
+             * Content data that is common for all item types.
+             *
+             * @property name The name of the item.
+             * @property lastUpdated A formatted date string indicating when the item was last
+             * updated.
+             * @property notes Contains general notes taken by the user.
+             * @property isPremiumUser Indicates if the user has subscribed to a premium account.
+             * @property customFields A list of custom fields that user has added.
+             * @property requiresReprompt Indicates if a master password prompt is required to view
+             * secure fields.
              */
             @Parcelize
-            data class Login(
-                override val name: String,
-                override val lastUpdated: String,
-                override val passwordHistoryCount: Int?,
-                override val notes: String?,
-                override val isPremiumUser: Boolean,
-                override val customFields: List<Custom>,
-                override val requiresReprompt: Boolean,
-                val username: String?,
-                val passwordData: PasswordData?,
-                val uris: List<UriData>,
-                val passwordRevisionDate: String?,
-                val totp: String?,
-            ) : Content()
+            data class Common(
+                val name: String,
+                val lastUpdated: String,
+                val notes: String?,
+                val isPremiumUser: Boolean,
+                val customFields: List<Custom>,
+                val requiresReprompt: Boolean,
+            ) : Parcelable {
+
+                /**
+                 * Represents a custom field, TextField, HiddenField, BooleanField, or LinkedField.
+                 */
+                sealed class Custom : Parcelable {
+                    /**
+                     * Represents the data for displaying a custom text field.
+                     */
+                    @Parcelize
+                    data class TextField(
+                        val name: String,
+                        val value: String,
+                        val isCopyable: Boolean,
+                    ) : Custom()
+
+                    /**
+                     * Represents the data for displaying a custom hidden text field.
+                     */
+                    @Parcelize
+                    data class HiddenField(
+                        val name: String,
+                        val value: String,
+                        val isCopyable: Boolean,
+                        val isVisible: Boolean,
+                    ) : Custom()
+
+                    /**
+                     * Represents the data for displaying a custom boolean property field.
+                     */
+                    @Parcelize
+                    data class BooleanField(
+                        val name: String,
+                        val value: Boolean,
+                    ) : Custom()
+
+                    /**
+                     * Represents the data for displaying a custom linked field.
+                     */
+                    @Parcelize
+                    data class LinkedField(
+                        val vaultLinkedFieldType: VaultLinkedFieldType,
+                        val name: String,
+                    ) : Custom()
+                }
+            }
 
             /**
-             * A wrapper for the password data, this includes the [password] itself and whether it
-             * should be visible.
+             * Content data specific to an item type.
              */
             @Parcelize
-            data class PasswordData(
-                val password: String,
-                val isVisible: Boolean,
-            ) : Parcelable
-
-            /**
-             * A wrapper for URI data, including the [uri] itself and whether it is copyable and
-             * launchable.
-             */
-            @Parcelize
-            data class UriData(
-                val uri: String,
-                val isCopyable: Boolean,
-                val isLaunchable: Boolean,
-            ) : Parcelable
-
-            /**
-             * Represents a custom field, TextField, HiddenField, BooleanField, or LinkedField.
-             */
-            sealed class Custom : Parcelable {
-                /**
-                 * Represents the data for displaying a custom text field.
-                 */
-                @Parcelize
-                data class TextField(
-                    val name: String,
-                    val value: String,
-                    val isCopyable: Boolean,
-                ) : Custom()
+            sealed class ItemType : Parcelable {
 
                 /**
-                 * Represents the data for displaying a custom hidden text field.
+                 * Represents the `Login` item type.
+                 *
+                 * @property username The username required for the login item.
+                 * @property passwordData The password required for the login item.
+                 * @property passwordHistoryCount An integer indicating how many times the password
+                 * has been changed.
+                 * @property uris The URI associated with the login item.
+                 * @property passwordRevisionDate
+                 * @property totp
                  */
                 @Parcelize
-                data class HiddenField(
-                    val name: String,
-                    val value: String,
-                    val isCopyable: Boolean,
-                    val isVisible: Boolean,
-                ) : Custom()
+                data class Login(
+                    val username: String?,
+                    val passwordData: PasswordData?,
+                    val passwordHistoryCount: Int?,
+                    val uris: List<UriData>,
+                    val passwordRevisionDate: String?,
+                    val totp: String?,
+                ) : ItemType() {
+
+                    /**
+                     * A wrapper for the password data, this includes the [password] itself
+                     * and whether it should be visible.
+                     */
+                    @Parcelize
+                    data class PasswordData(
+                        val password: String,
+                        val isVisible: Boolean,
+                    ) : Parcelable
+
+                    /**
+                     * A wrapper for URI data, including the [uri] itself and whether it is
+                     * copyable and launch-able.
+                     */
+                    @Parcelize
+                    data class UriData(
+                        val uri: String,
+                        val isCopyable: Boolean,
+                        val isLaunchable: Boolean,
+                    ) : Parcelable
+                }
 
                 /**
-                 * Represents the data for displaying a custom boolean property field.
+                 * Represents the `SecureNote` item type.
                  */
-                @Parcelize
-                data class BooleanField(
-                    val name: String,
-                    val value: Boolean,
-                ) : Custom()
+                data object SecureNote : ItemType()
 
                 /**
-                 * Represents the data for displaying a custom linked field.
+                 * Represents the `Identity` item type.
                  */
-                @Parcelize
-                data class LinkedField(
-                    val vaultLinkedFieldType: VaultLinkedFieldType,
-                    val name: String,
-                ) : Custom()
+                data object Identity : ItemType()
+
+                /**
+                 * Represents the `Card` item type.
+                 */
+                data object Card : ItemType()
             }
         }
     }
@@ -631,102 +694,116 @@ sealed class VaultItemEvent {
 }
 
 /**
- * Represents a set of actions related view a vault item.
+ * Represents a set of actions related to viewing a vault item.
+ * Each subclass of this sealed class denotes a distinct action that can be taken.
  */
 sealed class VaultItemAction {
-    /**
-     * The user has clicked the close button.
-     */
-    data object CloseClick : VaultItemAction()
 
     /**
-     * The user has clicked to dismiss the dialog.
+     * Represents actions common across all item types.
      */
-    data object DismissDialogClick : VaultItemAction()
+    sealed class Common : VaultItemAction() {
 
-    /**
-     * The user has clicked the edit button.
-     */
-    data object EditClick : VaultItemAction()
-
-    /**
-     * The user has submitted their master password.
-     */
-    data class MasterPasswordSubmit(
-        val masterPassword: String,
-    ) : VaultItemAction()
-
-    /**
-     * The user has clicked the refresh button.
-     */
-    data object RefreshClick : VaultItemAction()
-
-    /**
-     * Models actions that are associated with the [VaultItemState.ViewState.Content.Login] state.
-     */
-    sealed class Login : VaultItemAction() {
         /**
-         * The user has clicked the check for breach button.
+         * The user has clicked the close button.
          */
-        data object CheckForBreachClick : Login()
+        data object CloseClick : Common()
+
+        /**
+         * The user has clicked to dismiss the dialog.
+         */
+        data object DismissDialogClick : Common()
+
+        /**
+         * The user has clicked the edit button.
+         */
+        data object EditClick : Common()
+
+        /**
+         * The user has submitted their master password.
+         */
+        data class MasterPasswordSubmit(
+            val masterPassword: String,
+        ) : Common()
+
+        /**
+         * The user has clicked the refresh button.
+         */
+        data object RefreshClick : Common()
 
         /**
          * The user has clicked the copy button for a custom hidden field.
          */
         data class CopyCustomHiddenFieldClick(
             val field: String,
-        ) : Login()
+        ) : Common()
 
         /**
          * The user has clicked the copy button for a custom text field.
          */
         data class CopyCustomTextFieldClick(
             val field: String,
-        ) : Login()
-
-        /**
-         * The user has clicked the copy button for the password.
-         */
-        data object CopyPasswordClick : Login()
-
-        /**
-         * The user has clicked the copy button for a URI.
-         */
-        data class CopyUriClick(
-            val uri: String,
-        ) : Login()
-
-        /**
-         * The user has clicked the copy button for the username.
-         */
-        data object CopyUsernameClick : Login()
-
-        /**
-         * The user has clicked the launch button for a URI.
-         */
-        data class LaunchClick(
-            val uri: String,
-        ) : Login()
-
-        /**
-         * The user has clicked the password history text.
-         */
-        data object PasswordHistoryClick : Login()
-
-        /**
-         * The user has clicked to display the password.
-         */
-        data class PasswordVisibilityClicked(
-            val isVisible: Boolean,
-        ) : Login()
+        ) : Common()
 
         /**
          * The user has clicked to display the a hidden field.
          */
         data class HiddenFieldVisibilityClicked(
-            val field: VaultItemState.ViewState.Content.Custom.HiddenField,
+            val field: VaultItemState.ViewState.Content.Common.Custom.HiddenField,
             val isVisible: Boolean,
-        ) : Login()
+        ) : Common()
+    }
+
+    /**
+     * Represents actions specific to an item type.
+     */
+    sealed class ItemType : VaultItemAction() {
+
+        /**
+         * Represents actions specific to the Login type.
+         */
+        sealed class Login : ItemType() {
+            /**
+             * The user has clicked the check for breach button.
+             */
+            data object CheckForBreachClick : Login()
+
+            /**
+             * The user has clicked the copy button for the password.
+             */
+            data object CopyPasswordClick : Login()
+
+            /**
+             * The user has clicked the copy button for a URI.
+             */
+            data class CopyUriClick(
+                val uri: String,
+            ) : Login()
+
+            /**
+             * The user has clicked the copy button for the username.
+             */
+            data object CopyUsernameClick : Login()
+
+            /**
+             * The user has clicked the launch button for a URI.
+             */
+            data class LaunchClick(
+                val uri: String,
+            ) : Login()
+
+            /**
+             * The user has clicked the password history text.
+             */
+            data object PasswordHistoryClick : Login()
+
+            /**
+             * The user has clicked to display the password.
+             */
+            data class PasswordVisibilityClicked(
+                val isVisible: Boolean,
+            ) : Login()
+        }
     }
 
     /**
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultCommonItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultCommonItemTypeHandlers.kt
new file mode 100644
index 000000000..2ac0ec74d
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultCommonItemTypeHandlers.kt
@@ -0,0 +1,52 @@
+package com.x8bit.bitwarden.ui.vault.feature.item.handlers
+
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemAction
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
+
+/**
+ * A collection of handler functions for managing actions common within the context of viewing
+ * items in a vault.
+ *
+ * @property onCopyCustomHiddenField
+ * @property onCopyCustomTextField
+ * @property onShowHiddenFieldClick
+ */
+class VaultCommonItemTypeHandlers(
+    val onRefreshClick: () -> Unit,
+    val onCopyCustomHiddenField: (String) -> Unit,
+    val onCopyCustomTextField: (String) -> Unit,
+    val onShowHiddenFieldClick: (
+        VaultItemState.ViewState.Content.Common.Custom.HiddenField,
+        Boolean,
+    ) -> Unit,
+) {
+    companion object {
+        /**
+         * Creates an instance of [VaultCommonItemTypeHandlers] by binding actions
+         * to the provided [VaultItemViewModel].
+         */
+        fun create(
+            viewModel: VaultItemViewModel,
+        ): VaultCommonItemTypeHandlers =
+            VaultCommonItemTypeHandlers(
+                onRefreshClick = {
+                    viewModel.trySendAction(VaultItemAction.Common.RefreshClick)
+                },
+                onCopyCustomHiddenField = {
+                    viewModel.trySendAction(VaultItemAction.Common.CopyCustomHiddenFieldClick(it))
+                },
+                onCopyCustomTextField = {
+                    viewModel.trySendAction(VaultItemAction.Common.CopyCustomTextFieldClick(it))
+                },
+                onShowHiddenFieldClick = { customField, isVisible ->
+                    viewModel.trySendAction(
+                        VaultItemAction.Common.HiddenFieldVisibilityClicked(
+                            isVisible = isVisible,
+                            field = customField,
+                        ),
+                    )
+                },
+            )
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt
new file mode 100644
index 000000000..6784baabb
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt
@@ -0,0 +1,54 @@
+package com.x8bit.bitwarden.ui.vault.feature.item.handlers
+
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemAction
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
+
+/**
+ * A collection of handler functions for managing actions within the context of viewing identity
+ * items in a vault.
+ */
+@Suppress("LongParameterList")
+class VaultLoginItemTypeHandlers(
+    val onCheckForBreachClick: () -> Unit,
+    val onCopyPasswordClick: () -> Unit,
+    val onCopyUriClick: (String) -> Unit,
+    val onCopyUsernameClick: () -> Unit,
+    val onLaunchUriClick: (String) -> Unit,
+    val onPasswordHistoryClick: () -> Unit,
+    val onShowPasswordClick: (isVisible: Boolean) -> Unit,
+) {
+    companion object {
+        /**
+         * Creates the [VaultLoginItemTypeHandlers] using the [viewModel] to send desired actions.
+         */
+        @Suppress("LongMethod")
+        fun create(
+            viewModel: VaultItemViewModel,
+        ): VaultLoginItemTypeHandlers =
+            VaultLoginItemTypeHandlers(
+                onCheckForBreachClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CheckForBreachClick)
+                },
+                onCopyPasswordClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyPasswordClick)
+                },
+                onCopyUriClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUriClick(it))
+                },
+                onCopyUsernameClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUsernameClick)
+                },
+                onLaunchUriClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.LaunchClick(it))
+                },
+                onPasswordHistoryClick = {
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.PasswordHistoryClick)
+                },
+                onShowPasswordClick = {
+                    viewModel.trySendAction(
+                        VaultItemAction.ItemType.Login.PasswordVisibilityClicked(it),
+                    )
+                },
+            )
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
index eb94f07da..bc09ea3cc 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
@@ -7,11 +7,10 @@ import com.bitwarden.core.FieldType
 import com.bitwarden.core.FieldView
 import com.bitwarden.core.LoginUriView
 import com.x8bit.bitwarden.data.vault.repository.model.VaultData
-import com.x8bit.bitwarden.ui.platform.base.util.asText
 import com.x8bit.bitwarden.ui.platform.base.util.orZeroWidthSpace
-import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
 import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
 import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
+import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
 import java.time.format.DateTimeFormatter
 import java.util.TimeZone
 
@@ -26,70 +25,77 @@ private val dateTimeFormatter
 fun CipherView.toViewState(
     isPremiumUser: Boolean,
 ): VaultItemState.ViewState =
+    VaultItemState.ViewState.Content(
+        common = VaultItemState.ViewState.Content.Common(
+            name = name,
+            isPremiumUser = isPremiumUser,
+            requiresReprompt = reprompt == CipherRepromptType.PASSWORD,
+            customFields = fields.orEmpty().map { it.toCustomField() },
+            lastUpdated = dateTimeFormatter.format(revisionDate),
+            notes = notes,
+        ),
+        type = when (type) {
+            CipherType.LOGIN -> {
+                val loginValues = requireNotNull(login)
+                VaultItemState.ViewState.Content.ItemType.Login(
+                    username = loginValues.username,
+                    passwordData = loginValues.password?.let {
+                        VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
+                            password = it,
+                            isVisible = false,
+                        )
+                    },
+                    uris = loginValues.uris.orEmpty().map { it.toUriData() },
+                    passwordRevisionDate = loginValues.passwordRevisionDate?.let {
+                        dateTimeFormatter.format(it)
+                    },
+                    passwordHistoryCount = passwordHistory?.count(),
+                    totp = loginValues.totp,
+                )
+            }
+
+            CipherType.SECURE_NOTE -> {
+                VaultItemState.ViewState.Content.ItemType.SecureNote
+            }
+
+            CipherType.CARD -> {
+                VaultItemState.ViewState.Content.ItemType.Card
+            }
+
+            CipherType.IDENTITY -> {
+                VaultItemState.ViewState.Content.ItemType.Identity
+            }
+        },
+    )
+
+private fun FieldView.toCustomField(): VaultItemState.ViewState.Content.Common.Custom =
     when (type) {
-        CipherType.LOGIN -> {
-            val loginValues = requireNotNull(this.login)
-            VaultItemState.ViewState.Content.Login(
-                name = this.name,
-                username = loginValues.username,
-                passwordData = loginValues.password?.let {
-                    VaultItemState.ViewState.Content.PasswordData(password = it, isVisible = false)
-                },
-                isPremiumUser = isPremiumUser,
-                requiresReprompt = this.reprompt == CipherRepromptType.PASSWORD,
-                customFields = this.fields.orEmpty().map { it.toCustomField() },
-                uris = loginValues.uris.orEmpty().map { it.toUriData() },
-                lastUpdated = dateTimeFormatter.format(this.revisionDate),
-                passwordRevisionDate = loginValues.passwordRevisionDate?.let {
-                    dateTimeFormatter.format(it)
-                },
-                passwordHistoryCount = this.passwordHistory?.count(),
-                totp = loginValues.totp,
-                notes = this.notes,
-            )
-        }
-
-        CipherType.SECURE_NOTE -> VaultItemState.ViewState.Error(
-            message = "Not yet implemented.".asText(),
-        )
-
-        CipherType.CARD -> VaultItemState.ViewState.Error(
-            message = "Not yet implemented.".asText(),
-        )
-
-        CipherType.IDENTITY -> VaultItemState.ViewState.Error(
-            message = "Not yet implemented.".asText(),
-        )
-    }
-
-private fun FieldView.toCustomField(): VaultItemState.ViewState.Content.Custom =
-    when (type) {
-        FieldType.TEXT -> VaultItemState.ViewState.Content.Custom.TextField(
+        FieldType.TEXT -> VaultItemState.ViewState.Content.Common.Custom.TextField(
             name = name.orEmpty(),
             value = value.orZeroWidthSpace(),
             isCopyable = !value.isNullOrBlank(),
         )
 
-        FieldType.HIDDEN -> VaultItemState.ViewState.Content.Custom.HiddenField(
+        FieldType.HIDDEN -> VaultItemState.ViewState.Content.Common.Custom.HiddenField(
             name = name.orEmpty(),
             value = value.orZeroWidthSpace(),
             isCopyable = !value.isNullOrBlank(),
             isVisible = false,
         )
 
-        FieldType.BOOLEAN -> VaultItemState.ViewState.Content.Custom.BooleanField(
+        FieldType.BOOLEAN -> VaultItemState.ViewState.Content.Common.Custom.BooleanField(
             name = name.orEmpty(),
             value = value?.toBoolean() ?: false,
         )
 
-        FieldType.LINKED -> VaultItemState.ViewState.Content.Custom.LinkedField(
+        FieldType.LINKED -> VaultItemState.ViewState.Content.Common.Custom.LinkedField(
             vaultLinkedFieldType = VaultLinkedFieldType.fromId(requireNotNull(linkedId)),
             name = name.orEmpty(),
         )
     }
 
 private fun LoginUriView.toUriData() =
-    VaultItemState.ViewState.Content.UriData(
+    VaultItemState.ViewState.Content.ItemType.Login.UriData(
         uri = uri.orZeroWidthSpace(),
         isCopyable = !uri.isNullOrBlank(),
         isLaunchable = !uri.isNullOrBlank(),
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
index 09eac2db5..5cd5cf0ef 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
@@ -37,6 +37,7 @@ import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 
+@Suppress("LargeClass")
 class VaultItemScreenTest : BaseComposeTest() {
 
     private var onNavigateBackCalled = false
@@ -79,7 +80,7 @@ class VaultItemScreenTest : BaseComposeTest() {
         composeTestRule.onNodeWithContentDescription(label = "Close").performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.CloseClick)
+            viewModel.trySendAction(VaultItemAction.Common.CloseClick)
         }
     }
 
@@ -143,7 +144,7 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.DismissDialogClick)
+            viewModel.trySendAction(VaultItemAction.Common.DismissDialogClick)
         }
     }
 
@@ -191,15 +192,26 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.MasterPasswordSubmit(enteredPassword))
+            viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit(enteredPassword))
         }
     }
 
     @Test
     fun `in login state, on username copy click should send CopyUsernameClick`() {
         val username = "username1234"
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(username = username))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = VaultItemState.ViewState.Content.ItemType.Login(
+                        username = username,
+                        passwordData = null,
+                        passwordHistoryCount = null,
+                        uris = emptyList(),
+                        passwordRevisionDate = null,
+                        totp = null,
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -209,18 +221,24 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.CopyUsernameClick)
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUsernameClick)
         }
     }
 
     @Test
     fun `in login state, on breach check click should send CheckForBreachClick`() {
-        val passwordData = VaultItemState.ViewState.Content.PasswordData(
+        val passwordData = VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
             password = "12345",
             isVisible = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(passwordData = passwordData))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        passwordData = passwordData,
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -230,7 +248,7 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.CheckForBreachClick)
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.CheckForBreachClick)
         }
     }
 
@@ -245,18 +263,24 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify(exactly = 1) {
-            viewModel.trySendAction(VaultItemAction.Login.PasswordVisibilityClicked(true))
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.PasswordVisibilityClicked(true))
         }
     }
 
     @Test
     fun `in login state, on copy password click should send CopyPasswordClick`() {
-        val passwordData = VaultItemState.ViewState.Content.PasswordData(
+        val passwordData = VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
             password = "12345",
             isVisible = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(passwordData = passwordData))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        passwordData = passwordData,
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -266,19 +290,25 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.CopyPasswordClick)
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyPasswordClick)
         }
     }
 
     @Test
     fun `in login state, launch uri button should be displayed according to state`() {
-        val uriData = VaultItemState.ViewState.Content.UriData(
+        val uriData = VaultItemState.ViewState.Content.ItemType.Login.UriData(
             uri = "www.example.com",
             isCopyable = true,
             isLaunchable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(uris = listOf(uriData)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        uris = listOf(uriData),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -287,12 +317,10 @@ class VaultItemScreenTest : BaseComposeTest() {
             .filterToOne(hasContentDescription("Launch"))
             .assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(
-                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
-                    uris = listOf(uriData.copy(isLaunchable = false)),
-                ),
-            )
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) {
+                copy(uris = listOf(uriData.copy(isLaunchable = false)))
+            }
         }
 
         composeTestRule
@@ -304,13 +332,19 @@ class VaultItemScreenTest : BaseComposeTest() {
 
     @Test
     fun `in login state, copy uri button should be displayed according to state`() {
-        val uriData = VaultItemState.ViewState.Content.UriData(
+        val uriData = VaultItemState.ViewState.Content.ItemType.Login.UriData(
             uri = "www.example.com",
             isCopyable = true,
             isLaunchable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(uris = listOf(uriData)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        uris = listOf(uriData),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -319,12 +353,8 @@ class VaultItemScreenTest : BaseComposeTest() {
             .filterToOne(hasContentDescription("Copy"))
             .assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(
-                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
-                    uris = listOf(uriData.copy(isCopyable = false)),
-                ),
-            )
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) { copy(uris = listOf(uriData.copy(isCopyable = false))) }
         }
 
         composeTestRule
@@ -336,13 +366,19 @@ class VaultItemScreenTest : BaseComposeTest() {
 
     @Test
     fun `in login state, on launch URI click should send LaunchClick`() {
-        val uriData = VaultItemState.ViewState.Content.UriData(
+        val uriData = VaultItemState.ViewState.Content.ItemType.Login.UriData(
             uri = "www.example.com",
             isCopyable = true,
             isLaunchable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(uris = listOf(uriData)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        uris = listOf(uriData),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -352,19 +388,25 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.LaunchClick(uriData.uri))
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.LaunchClick(uriData.uri))
         }
     }
 
     @Test
     fun `in login state, on copy URI click should send CopyUriClick`() {
-        val uriData = VaultItemState.ViewState.Content.UriData(
+        val uriData = VaultItemState.ViewState.Content.ItemType.Login.UriData(
             uri = "www.example.com",
             isCopyable = true,
             isLaunchable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(uris = listOf(uriData)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        uris = listOf(uriData),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -374,20 +416,26 @@ class VaultItemScreenTest : BaseComposeTest() {
             .performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.CopyUriClick(uriData.uri))
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUriClick(uriData.uri))
         }
     }
 
     @Test
-    fun `in login state, on show hidden field click should send HiddenFieldVisibilityClicked`() {
-        val textField = VaultItemState.ViewState.Content.Custom.HiddenField(
+    fun `on show hidden field click should send HiddenFieldVisibilityClicked`() {
+        val textField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
             name = "hidden",
             value = "hidden password",
             isCopyable = true,
             isVisible = false,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(customFields = listOf(textField)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = EMPTY_COMMON.copy(
+                        customFields = listOf(textField),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -398,7 +446,7 @@ class VaultItemScreenTest : BaseComposeTest() {
 
         verify {
             viewModel.trySendAction(
-                VaultItemAction.Login.HiddenFieldVisibilityClicked(
+                VaultItemAction.Common.HiddenFieldVisibilityClicked(
                     field = textField,
                     isVisible = true,
                 ),
@@ -407,15 +455,21 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
 
     @Test
-    fun `in login state, copy hidden field button should be displayed according to state`() {
-        val hiddenField = VaultItemState.ViewState.Content.Custom.HiddenField(
+    fun `copy hidden field button should be displayed according to state`() {
+        val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
             name = "hidden",
             value = "hidden password",
             isCopyable = true,
             isVisible = false,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(customFields = listOf(hiddenField)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = EMPTY_COMMON.copy(
+                        customFields = listOf(hiddenField),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -424,12 +478,10 @@ class VaultItemScreenTest : BaseComposeTest() {
             .filterToOne(hasContentDescription("Copy"))
             .assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(
-                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
-                    customFields = listOf(hiddenField.copy(isCopyable = false)),
-                ),
-            )
+        mutableStateFlow.update { currentState ->
+            updateCommonContent(currentState) {
+                copy(customFields = listOf(hiddenField.copy(isCopyable = false)))
+            }
         }
 
         composeTestRule
@@ -440,15 +492,21 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
 
     @Test
-    fun `in login state, on copy hidden field click should send CopyCustomHiddenFieldClick`() {
-        val hiddenField = VaultItemState.ViewState.Content.Custom.HiddenField(
+    fun `on copy hidden field click should send CopyCustomHiddenFieldClick`() {
+        val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
             name = "hidden",
             value = "hidden password",
             isCopyable = true,
             isVisible = false,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(customFields = listOf(hiddenField)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = EMPTY_COMMON.copy(
+                        customFields = listOf(hiddenField),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -459,20 +517,26 @@ class VaultItemScreenTest : BaseComposeTest() {
 
         verify {
             viewModel.trySendAction(
-                VaultItemAction.Login.CopyCustomHiddenFieldClick(hiddenField.value),
+                VaultItemAction.Common.CopyCustomHiddenFieldClick(hiddenField.value),
             )
         }
     }
 
     @Test
-    fun `in login state, on copy text field click should send CopyCustomTextFieldClick`() {
-        val textField = VaultItemState.ViewState.Content.Custom.TextField(
+    fun `on copy text field click should send CopyCustomTextFieldClick`() {
+        val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
             name = "text",
             value = "value",
             isCopyable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(customFields = listOf(textField)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = EMPTY_COMMON.copy(
+                        customFields = listOf(textField),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -483,20 +547,26 @@ class VaultItemScreenTest : BaseComposeTest() {
 
         verify {
             viewModel.trySendAction(
-                VaultItemAction.Login.CopyCustomTextFieldClick(textField.value),
+                VaultItemAction.Common.CopyCustomTextFieldClick(textField.value),
             )
         }
     }
 
     @Test
-    fun `in login state, text field copy button should be displayed according to state`() {
-        val textField = VaultItemState.ViewState.Content.Custom.TextField(
+    fun `text field copy button should be displayed according to state`() {
+        val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
             name = "text",
             value = "value",
             isCopyable = true,
         )
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(customFields = listOf(textField)))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = EMPTY_COMMON.copy(
+                        customFields = listOf(textField),
+                    ),
+                ),
+            )
         }
 
         composeTestRule
@@ -505,12 +575,10 @@ class VaultItemScreenTest : BaseComposeTest() {
             .filterToOne(hasContentDescription("Copy"))
             .assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(
-                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
-                    customFields = listOf(textField.copy(isCopyable = false)),
-                ),
-            )
+        mutableStateFlow.update { currentState ->
+            updateCommonContent(currentState) {
+                copy(customFields = listOf(textField.copy(isCopyable = false)))
+            }
         }
 
         composeTestRule
@@ -522,15 +590,21 @@ class VaultItemScreenTest : BaseComposeTest() {
 
     @Test
     fun `in login state, on password history click should send PasswordHistoryClick`() {
-        mutableStateFlow.update {
-            it.copy(viewState = EMPTY_LOGIN_VIEW_STATE.copy(passwordHistoryCount = 5))
+        mutableStateFlow.update { currentState ->
+            currentState.copy(
+                viewState = EMPTY_LOGIN_VIEW_STATE.copy(
+                    type = EMPTY_LOGIN_TYPE.copy(
+                        passwordHistoryCount = 5,
+                    ),
+                ),
+            )
         }
 
         composeTestRule.onNodeWithTextAfterScroll("5")
         composeTestRule.onNodeWithText("5").performClick()
 
         verify {
-            viewModel.trySendAction(VaultItemAction.Login.PasswordHistoryClick)
+            viewModel.trySendAction(VaultItemAction.ItemType.Login.PasswordHistoryClick)
         }
     }
 
@@ -551,7 +625,7 @@ class VaultItemScreenTest : BaseComposeTest() {
         }
         composeTestRule.onNodeWithContentDescription("Edit item").performClick()
         verify(exactly = 1) {
-            viewModel.trySendAction(VaultItemAction.EditClick)
+            viewModel.trySendAction(VaultItemAction.Common.EditClick)
         }
     }
 
@@ -590,8 +664,8 @@ class VaultItemScreenTest : BaseComposeTest() {
         mutableStateFlow.update { it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE) }
         composeTestRule.onNodeWithTextAfterScroll(username).assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(username = null))
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) { copy(username = null) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist(username)
@@ -604,8 +678,8 @@ class VaultItemScreenTest : BaseComposeTest() {
         composeTestRule.onNodeWithTextAfterScroll("URI").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("www.example.com").assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(uris = emptyList()))
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) { copy(uris = emptyList()) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist("URIs")
@@ -614,13 +688,13 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
 
     @Test
-    fun `in login state, notes should be displayed according to state`() {
+    fun `notes should be displayed according to state`() {
         mutableStateFlow.update { it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE) }
         composeTestRule.onFirstNodeWithTextAfterScroll("Notes").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("Lots of notes").assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(notes = null))
+        mutableStateFlow.update { currentState ->
+            updateCommonContent(currentState) { copy(notes = null) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist("Notes")
@@ -628,7 +702,7 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
 
     @Test
-    fun `in login state, custom views should be displayed according to state`() {
+    fun `custom views should be displayed according to state`() {
         mutableStateFlow.update { it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE) }
         composeTestRule.onNodeWithTextAfterScroll("Custom fields").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("text").assertIsDisplayed()
@@ -638,8 +712,8 @@ class VaultItemScreenTest : BaseComposeTest() {
         composeTestRule.onNodeWithTextAfterScroll("linked username").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("linked password").assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(customFields = emptyList()))
+        mutableStateFlow.update { currentState ->
+            updateCommonContent(currentState) { copy(customFields = emptyList()) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist("Custom fields")
@@ -657,8 +731,8 @@ class VaultItemScreenTest : BaseComposeTest() {
         composeTestRule.onNodeWithTextAfterScroll("Password updated: ").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("4/14/83 3:56 PM").assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(passwordRevisionDate = null))
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) { copy(passwordRevisionDate = null) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist("Password updated: ")
@@ -671,8 +745,8 @@ class VaultItemScreenTest : BaseComposeTest() {
         composeTestRule.onNodeWithTextAfterScroll("Password history: ").assertIsDisplayed()
         composeTestRule.onNodeWithTextAfterScroll("1").assertIsDisplayed()
 
-        mutableStateFlow.update {
-            it.copy(viewState = DEFAULT_LOGIN_VIEW_STATE.copy(passwordHistoryCount = null))
+        mutableStateFlow.update { currentState ->
+            updateLoginType(currentState) { copy(passwordHistoryCount = null) }
         }
 
         composeTestRule.assertScrollableNodeDoesNotExist("Password history: ")
@@ -680,6 +754,49 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
 }
 
+//region Helper functions
+
+@Suppress("MaxLineLength")
+private fun updateLoginType(
+    currentState: VaultItemState,
+    transform: VaultItemState.ViewState.Content.ItemType.Login.() ->
+    VaultItemState.ViewState.Content.ItemType.Login,
+): VaultItemState {
+    val updatedType = when (val viewState = currentState.viewState) {
+        is VaultItemState.ViewState.Content -> {
+            when (val type = viewState.type) {
+                is VaultItemState.ViewState.Content.ItemType.Login -> {
+                    viewState.copy(
+                        type = type.transform(),
+                    )
+                }
+
+                else -> viewState
+            }
+        }
+
+        else -> viewState
+    }
+    return currentState.copy(viewState = updatedType)
+}
+
+@Suppress("MaxLineLength")
+private fun updateCommonContent(
+    currentState: VaultItemState,
+    transform: VaultItemState.ViewState.Content.Common.()
+    -> VaultItemState.ViewState.Content.Common,
+): VaultItemState {
+    val updatedType = when (val viewState = currentState.viewState) {
+        is VaultItemState.ViewState.Content ->
+            viewState.copy(common = viewState.common.transform())
+
+        else -> viewState
+    }
+    return currentState.copy(viewState = updatedType)
+}
+
+//endregion Helper functions
+
 private const val VAULT_ITEM_ID = "vault_item_id"
 
 private val DEFAULT_STATE: VaultItemState = VaultItemState(
@@ -688,67 +805,81 @@ private val DEFAULT_STATE: VaultItemState = VaultItemState(
     dialog = null,
 )
 
-private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
-    VaultItemState.ViewState.Content.Login(
-        name = "login cipher",
-        lastUpdated = "12/31/69 06:16 PM",
-        passwordHistoryCount = 1,
-        notes = "Lots of notes",
-        isPremiumUser = true,
-        customFields = listOf(
-            VaultItemState.ViewState.Content.Custom.TextField(
-                name = "text",
-                value = "value",
-                isCopyable = true,
-            ),
-            VaultItemState.ViewState.Content.Custom.HiddenField(
-                name = "hidden",
-                value = "hidden password",
-                isCopyable = true,
+private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content =
+    VaultItemState.ViewState.Content(
+        type = VaultItemState.ViewState.Content.ItemType.Login(
+            passwordHistoryCount = 1,
+            username = "the username",
+            passwordData = VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
+                password = "the password",
                 isVisible = false,
             ),
-            VaultItemState.ViewState.Content.Custom.BooleanField(
-                name = "boolean",
-                value = true,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked username",
-                vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked password",
-                vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
+            uris = listOf(
+                VaultItemState.ViewState.Content.ItemType.Login.UriData(
+                    uri = "www.example.com",
+                    isCopyable = true,
+                    isLaunchable = true,
+                ),
             ),
+            passwordRevisionDate = "4/14/83 3:56 PM",
+            totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
         ),
-        requiresReprompt = true,
-        username = "the username",
-        passwordData = VaultItemState.ViewState.Content.PasswordData(
-            password = "the password",
-            isVisible = false,
-        ),
-        uris = listOf(
-            VaultItemState.ViewState.Content.UriData(
-                uri = "www.example.com",
-                isCopyable = true,
-                isLaunchable = true,
+        common = VaultItemState.ViewState.Content.Common(
+            lastUpdated = "12/31/69 06:16 PM",
+            name = "login cipher",
+            notes = "Lots of notes",
+            isPremiumUser = true,
+            customFields = listOf(
+                VaultItemState.ViewState.Content.Common.Custom.TextField(
+                    name = "text",
+                    value = "value",
+                    isCopyable = true,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.HiddenField(
+                    name = "hidden",
+                    value = "hidden password",
+                    isCopyable = true,
+                    isVisible = false,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.BooleanField(
+                    name = "boolean",
+                    value = true,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                    name = "linked username",
+                    vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                    name = "linked password",
+                    vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
+                ),
             ),
+            requiresReprompt = true,
         ),
-        passwordRevisionDate = "4/14/83 3:56 PM",
-        totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
     )
 
-private val EMPTY_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
-    VaultItemState.ViewState.Content.Login(
+private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
+    VaultItemState.ViewState.Content.Common(
         name = "login cipher",
         lastUpdated = "12/31/69 06:16 PM",
-        passwordHistoryCount = null,
         notes = null,
         isPremiumUser = true,
         customFields = emptyList(),
         requiresReprompt = true,
+    )
+
+private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
+    VaultItemState.ViewState.Content.ItemType.Login(
         username = null,
         passwordData = null,
+        passwordHistoryCount = null,
         uris = emptyList(),
         passwordRevisionDate = null,
         totp = null,
     )
+
+private val EMPTY_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content =
+    VaultItemState.ViewState.Content(
+        common = EMPTY_COMMON,
+        type = EMPTY_LOGIN_TYPE,
+    )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
index 327207cb5..0c4998c11 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
@@ -71,114 +71,264 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         assertEquals(state, viewModel.stateFlow.value)
     }
 
-    @Test
-    fun `on CloseClick should emit NavigateBack`() = runTest {
-        val viewModel = createViewModel(state = DEFAULT_STATE)
-        viewModel.eventFlow.test {
-            viewModel.trySendAction(VaultItemAction.CloseClick)
-            assertEquals(VaultItemEvent.NavigateBack, awaitItem())
-        }
-    }
+    @Nested
+    inner class CommonActions {
+        private lateinit var viewModel: VaultItemViewModel
 
-    @Test
-    fun `on DismissDialogClick should clear the dialog state`() = runTest {
-        val initialState = DEFAULT_STATE.copy(dialog = VaultItemState.DialogState.Loading)
-        val viewModel = createViewModel(state = initialState)
-        assertEquals(initialState, viewModel.stateFlow.value)
-
-        viewModel.trySendAction(VaultItemAction.DismissDialogClick)
-        assertEquals(initialState.copy(dialog = null), viewModel.stateFlow.value)
-    }
-
-    @Test
-    fun `on EditClick should do nothing when ViewState is not Content`() = runTest {
-        val initialState = DEFAULT_STATE
-        val viewModel = createViewModel(state = initialState)
-
-        assertEquals(initialState, viewModel.stateFlow.value)
-        viewModel.eventFlow.test {
-            viewModel.trySendAction(VaultItemAction.EditClick)
-            expectNoEvents()
-        }
-        assertEquals(initialState, viewModel.stateFlow.value)
-    }
-
-    @Test
-    fun `on EditClick should prompt for master password when required`() = runTest {
-        val mockCipherView = mockk<CipherView> {
-            every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
-        }
-        mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-        val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
-        val viewModel = createViewModel(state = loginState)
-        assertEquals(loginState, viewModel.stateFlow.value)
-
-        viewModel.trySendAction(VaultItemAction.EditClick)
-        assertEquals(
-            loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
-            viewModel.stateFlow.value,
-        )
-
-        verify(exactly = 1) {
-            mockCipherView.toViewState(isPremiumUser = true)
-        }
-    }
-
-    @Test
-    fun `on EditClick should navigate password is not required`() = runTest {
-        val loginViewState = DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
-        val mockCipherView = mockk<CipherView> {
-            every { toViewState(isPremiumUser = true) } returns loginViewState
-        }
-        mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-        val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
-        val viewModel = createViewModel(state = loginState)
-
-        viewModel.eventFlow.test {
-            viewModel.trySendAction(VaultItemAction.EditClick)
-            assertEquals(VaultItemEvent.NavigateToEdit(VAULT_ITEM_ID), awaitItem())
-        }
-
-        verify(exactly = 1) {
-            mockCipherView.toViewState(isPremiumUser = true)
-        }
-    }
-
-    @Test
-    fun `on MasterPasswordSubmit should verify the password`() = runTest {
-        val loginViewState = DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
-        val mockCipherView = mockk<CipherView> {
-            every { toViewState(isPremiumUser = true) } returns loginViewState
-        }
-        mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-        val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
-        val viewModel = createViewModel(state = loginState)
-
-        viewModel.stateFlow.test {
-            assertEquals(loginState, awaitItem())
-            viewModel.trySendAction(VaultItemAction.MasterPasswordSubmit("password"))
-            assertEquals(loginState.copy(dialog = VaultItemState.DialogState.Loading), awaitItem())
-            assertEquals(
-                loginState.copy(viewState = loginViewState.copy(requiresReprompt = false)),
-                awaitItem(),
+        @BeforeEach
+        fun setup() {
+            viewModel = createViewModel(
+                state = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE),
             )
         }
 
-        verify(exactly = 1) {
-            mockCipherView.toViewState(isPremiumUser = true)
+        @Test
+        fun `on CloseClick should emit NavigateBack`() = runTest {
+            val viewModel = createViewModel(state = DEFAULT_STATE)
+            viewModel.eventFlow.test {
+                viewModel.trySendAction(VaultItemAction.Common.CloseClick)
+                assertEquals(VaultItemEvent.NavigateBack, awaitItem())
+            }
         }
-    }
 
-    @Test
-    fun `on RefreshClick should sync`() = runTest {
-        every { vaultRepo.sync() } just runs
-        val viewModel = createViewModel(state = DEFAULT_STATE)
+        @Test
+        fun `on DismissDialogClick should clear the dialog state`() = runTest {
+            val initialState = DEFAULT_STATE.copy(dialog = VaultItemState.DialogState.Loading)
+            val viewModel = createViewModel(state = initialState)
+            assertEquals(initialState, viewModel.stateFlow.value)
 
-        viewModel.trySendAction(VaultItemAction.RefreshClick)
-
-        verify(exactly = 1) {
-            vaultRepo.sync()
+            viewModel.trySendAction(VaultItemAction.Common.DismissDialogClick)
+            assertEquals(initialState.copy(dialog = null), viewModel.stateFlow.value)
         }
+
+        @Test
+        fun `on EditClick should do nothing when ViewState is not Content`() = runTest {
+            val initialState = DEFAULT_STATE
+            val viewModel = createViewModel(state = initialState)
+
+            assertEquals(initialState, viewModel.stateFlow.value)
+            viewModel.eventFlow.test {
+                viewModel.trySendAction(VaultItemAction.Common.EditClick)
+                expectNoEvents()
+            }
+            assertEquals(initialState, viewModel.stateFlow.value)
+        }
+
+        @Test
+        fun `on EditClick should prompt for master password when required`() = runTest {
+            val mockCipherView = mockk<CipherView> {
+                every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
+            }
+            mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+            val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
+            val viewModel = createViewModel(state = loginState)
+            assertEquals(loginState, viewModel.stateFlow.value)
+
+            viewModel.trySendAction(VaultItemAction.Common.EditClick)
+            assertEquals(
+                loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
+                viewModel.stateFlow.value,
+            )
+        }
+
+        @Test
+        fun `on EditClick should navigate password is not required`() = runTest {
+            val loginViewState = createViewState(
+                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+            )
+            val mockCipherView = mockk<CipherView> {
+                every { toViewState(isPremiumUser = true) } returns loginViewState
+            }
+            mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+            val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
+            val viewModel = createViewModel(state = loginState)
+
+            viewModel.eventFlow.test {
+                viewModel.trySendAction(VaultItemAction.Common.EditClick)
+                assertEquals(VaultItemEvent.NavigateToEdit(VAULT_ITEM_ID), awaitItem())
+            }
+        }
+
+        @Test
+        fun `on MasterPasswordSubmit should verify the password`() = runTest {
+            val loginViewState = createViewState(
+                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+            )
+            val mockCipherView = mockk<CipherView> {
+                every { toViewState(isPremiumUser = true) } returns loginViewState
+            }
+            mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+            val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
+            val viewModel = createViewModel(state = loginState)
+
+            viewModel.stateFlow.test {
+                assertEquals(loginState, awaitItem())
+                viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit("password"))
+                assertEquals(
+                    loginState.copy(dialog = VaultItemState.DialogState.Loading),
+                    awaitItem(),
+                )
+                assertEquals(
+                    loginState.copy(
+                        viewState = loginViewState.copy(
+                            common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                        ),
+                    ),
+                    awaitItem(),
+                )
+            }
+        }
+
+        @Test
+        fun `on RefreshClick should sync`() = runTest {
+            every { vaultRepo.sync() } just runs
+            val viewModel = createViewModel(state = DEFAULT_STATE)
+
+            viewModel.trySendAction(VaultItemAction.Common.RefreshClick)
+
+            verify(exactly = 1) {
+                vaultRepo.sync()
+            }
+        }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `on CopyCustomHiddenFieldClick should show password dialog when re-prompt is required`() =
+            runTest {
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
+                val mockCipherView = mockk<CipherView> {
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
+                }
+                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+
+                assertEquals(loginState, viewModel.stateFlow.value)
+                viewModel.trySendAction(VaultItemAction.Common.CopyCustomHiddenFieldClick("field"))
+                assertEquals(
+                    loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
+                    viewModel.stateFlow.value,
+                )
+
+                verify(exactly = 1) {
+                    mockCipherView.toViewState(isPremiumUser = true)
+                }
+            }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `on CopyCustomHiddenFieldClick should emit CopyToClipboard when re-prompt is not required`() =
+            runTest {
+                val field = "field"
+                val mockCipherView = mockk<CipherView> {
+                    every {
+                        toViewState(isPremiumUser = true)
+                    }
+                        .returns(
+                            createViewState(
+                                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                            ),
+                        )
+                }
+                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+
+                viewModel.eventFlow.test {
+                    viewModel.trySendAction(VaultItemAction.Common.CopyCustomHiddenFieldClick(field))
+                    assertEquals(
+                        VaultItemEvent.CopyToClipboard(field.asText()),
+                        awaitItem(),
+                    )
+                }
+
+                verify(exactly = 1) {
+                    mockCipherView.toViewState(isPremiumUser = true)
+                }
+            }
+
+        @Test
+        fun `on CopyCustomTextFieldClick should emit CopyToClipboard`() = runTest {
+            val field = "field"
+            viewModel.eventFlow.test {
+                viewModel.trySendAction(VaultItemAction.Common.CopyCustomTextFieldClick(field))
+                assertEquals(VaultItemEvent.CopyToClipboard(field.asText()), awaitItem())
+            }
+        }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `on HiddenFieldVisibilityClicked should show password dialog when re-prompt is required`() =
+            runTest {
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
+                val mockCipherView = mockk<CipherView> {
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
+                }
+                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+
+                assertEquals(loginState, viewModel.stateFlow.value)
+                viewModel.trySendAction(
+                    VaultItemAction.Common.HiddenFieldVisibilityClicked(
+                        field = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
+                            name = "hidden",
+                            value = "value",
+                            isCopyable = true,
+                            isVisible = false,
+                        ),
+                        isVisible = true,
+                    ),
+                )
+                assertEquals(
+                    loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
+                    viewModel.stateFlow.value,
+                )
+
+                verify(exactly = 1) {
+                    mockCipherView.toViewState(isPremiumUser = true)
+                }
+            }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `on HiddenFieldVisibilityClicked should update hidden field visibility when re-prompt is not required`() =
+            runTest {
+                val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
+                    name = "hidden",
+                    value = "value",
+                    isCopyable = true,
+                    isVisible = false,
+                )
+                val loginViewState = DEFAULT_EMPTY_LOGIN_VIEW_STATE.copy(
+                    common = DEFAULT_EMPTY_LOGIN_VIEW_STATE.common.copy(
+                        requiresReprompt = false,
+                        customFields = listOf(hiddenField),
+                    ),
+                )
+                val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
+                val mockCipherView = mockk<CipherView> {
+                    every { toViewState(isPremiumUser = true) } returns loginViewState
+                }
+                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+
+                assertEquals(loginState, viewModel.stateFlow.value)
+                viewModel.trySendAction(
+                    VaultItemAction.Common.HiddenFieldVisibilityClicked(
+                        field = hiddenField,
+                        isVisible = true,
+                    ),
+                )
+                assertEquals(
+                    loginState.copy(
+                        viewState = loginViewState.copy(
+                            common = DEFAULT_EMPTY_LOGIN_VIEW_STATE.common.copy(
+                                requiresReprompt = false,
+                                customFields = listOf(hiddenField.copy(isVisible = true)),
+                            ),
+                        ),
+                    ),
+                    viewModel.stateFlow.value,
+                )
+
+                verify(exactly = 1) {
+                    mockCipherView.toViewState(isPremiumUser = true)
+                }
+            }
     }
 
     @Nested
@@ -188,17 +338,17 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @BeforeEach
         fun setup() {
             viewModel = createViewModel(
-                state = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE),
+                state = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE),
             )
         }
 
         @Test
         fun `on CheckForBreachClick should process a password`() = runTest {
             val mockCipherView = mockk<CipherView> {
-                every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
+                every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
             }
             mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-            val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
+            val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
             val breachCount = 5
             coEvery {
                 authRepo.getPasswordBreachCount(password = DEFAULT_LOGIN_PASSWORD)
@@ -206,7 +356,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
 
             viewModel.stateFlow.test {
                 assertEquals(loginState, awaitItem())
-                viewModel.trySendAction(VaultItemAction.Login.CheckForBreachClick)
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.CheckForBreachClick)
                 assertEquals(
                     loginState.copy(dialog = VaultItemState.DialogState.Loading),
                     awaitItem(),
@@ -232,14 +382,14 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @Test
         fun `on CopyPasswordClick should show password dialog when re-prompt is required`() =
             runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
                 val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.CopyPasswordClick)
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyPasswordClick)
                 assertEquals(
                     loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
                     viewModel.stateFlow.value,
@@ -256,12 +406,17 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 val mockCipherView = mockk<CipherView> {
                     every {
                         toViewState(isPremiumUser = true)
-                    } returns DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
+                    }
+                        .returns(
+                            createViewState(
+                                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                            ),
+                        )
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 viewModel.eventFlow.test {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyPasswordClick)
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyPasswordClick)
                     assertEquals(
                         VaultItemEvent.CopyToClipboard(DEFAULT_LOGIN_PASSWORD.asText()),
                         awaitItem(),
@@ -273,67 +428,11 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 }
             }
 
-        @Suppress("MaxLineLength")
-        @Test
-        fun `on CopyCustomHiddenFieldClick should show password dialog when re-prompt is required`() =
-            runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
-                val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
-                }
-                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-
-                assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.CopyCustomHiddenFieldClick("field"))
-                assertEquals(
-                    loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
-                    viewModel.stateFlow.value,
-                )
-
-                verify(exactly = 1) {
-                    mockCipherView.toViewState(isPremiumUser = true)
-                }
-            }
-
-        @Suppress("MaxLineLength")
-        @Test
-        fun `on CopyCustomHiddenFieldClick should emit CopyToClipboard when re-prompt is not required`() =
-            runTest {
-                val field = "field"
-                val mockCipherView = mockk<CipherView> {
-                    every {
-                        toViewState(isPremiumUser = true)
-                    } returns DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
-                }
-                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-
-                viewModel.eventFlow.test {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyCustomHiddenFieldClick(field))
-                    assertEquals(
-                        VaultItemEvent.CopyToClipboard(field.asText()),
-                        awaitItem(),
-                    )
-                }
-
-                verify(exactly = 1) {
-                    mockCipherView.toViewState(isPremiumUser = true)
-                }
-            }
-
-        @Test
-        fun `on CopyCustomTextFieldClick should emit CopyToClipboard`() = runTest {
-            val field = "field"
-            viewModel.eventFlow.test {
-                viewModel.trySendAction(VaultItemAction.Login.CopyCustomTextFieldClick(field))
-                assertEquals(VaultItemEvent.CopyToClipboard(field.asText()), awaitItem())
-            }
-        }
-
         @Test
         fun `on CopyUriClick should emit CopyToClipboard`() = runTest {
             val uri = "uri"
             viewModel.eventFlow.test {
-                viewModel.trySendAction(VaultItemAction.Login.CopyUriClick(uri))
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUriClick(uri))
                 assertEquals(VaultItemEvent.CopyToClipboard(uri.asText()), awaitItem())
             }
         }
@@ -341,14 +440,14 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @Test
         fun `on CopyUsernameClick should show password dialog when re-prompt is required`() =
             runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
                 val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.CopyUsernameClick)
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUsernameClick)
                 assertEquals(
                     loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
                     viewModel.stateFlow.value,
@@ -365,12 +464,17 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 val mockCipherView = mockk<CipherView> {
                     every {
                         toViewState(isPremiumUser = true)
-                    } returns DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
+                    }
+                        .returns(
+                            createViewState(
+                                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                            ),
+                        )
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 viewModel.eventFlow.test {
-                    viewModel.trySendAction(VaultItemAction.Login.CopyUsernameClick)
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyUsernameClick)
                     assertEquals(
                         VaultItemEvent.CopyToClipboard(DEFAULT_LOGIN_USERNAME.asText()),
                         awaitItem(),
@@ -386,7 +490,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         fun `on LaunchClick should emit NavigateToUri`() = runTest {
             val uri = "uri"
             viewModel.eventFlow.test {
-                viewModel.trySendAction(VaultItemAction.Login.LaunchClick(uri))
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.LaunchClick(uri))
                 assertEquals(VaultItemEvent.NavigateToUri(uri), awaitItem())
             }
         }
@@ -394,14 +498,14 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @Test
         fun `on PasswordHistoryClick should show password dialog when re-prompt is required`() =
             runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
                 val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.PasswordHistoryClick)
+                viewModel.trySendAction(VaultItemAction.ItemType.Login.PasswordHistoryClick)
                 assertEquals(
                     loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
                     viewModel.stateFlow.value,
@@ -419,12 +523,17 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 val mockCipherView = mockk<CipherView> {
                     every {
                         toViewState(isPremiumUser = true)
-                    } returns DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
+                    }
+                        .returns(
+                            createViewState(
+                                common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                            ),
+                        )
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 viewModel.eventFlow.test {
-                    viewModel.trySendAction(VaultItemAction.Login.PasswordHistoryClick)
+                    viewModel.trySendAction(VaultItemAction.ItemType.Login.PasswordHistoryClick)
                     assertEquals(
                         VaultItemEvent.NavigateToPasswordHistory(VAULT_ITEM_ID),
                         awaitItem(),
@@ -440,14 +549,18 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @Test
         fun `on PasswordVisibilityClicked should show password dialog when re-prompt is required`() =
             runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
+                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
                 val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
+                    every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
                 }
                 mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
 
                 assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.PasswordVisibilityClicked(true))
+                viewModel.trySendAction(
+                    VaultItemAction.ItemType.Login.PasswordVisibilityClicked(
+                        true,
+                    ),
+                )
                 assertEquals(
                     loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
                     viewModel.stateFlow.value,
@@ -462,74 +575,8 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         @Test
         fun `on PasswordVisibilityClicked should update password visibility when re-prompt is not required`() =
             runTest {
-                val loginViewState = DEFAULT_LOGIN_VIEW_STATE.copy(requiresReprompt = false)
-                val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
-                val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns loginViewState
-                }
-                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-
-                assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(VaultItemAction.Login.PasswordVisibilityClicked(true))
-                assertEquals(
-                    loginState.copy(
-                        viewState = loginViewState.copy(
-                            passwordData = loginViewState.passwordData!!.copy(isVisible = true),
-                        ),
-                    ),
-                    viewModel.stateFlow.value,
-                )
-
-                verify(exactly = 1) {
-                    mockCipherView.toViewState(isPremiumUser = true)
-                }
-            }
-
-        @Suppress("MaxLineLength")
-        @Test
-        fun `on HiddenFieldVisibilityClicked should show password dialog when re-prompt is required`() =
-            runTest {
-                val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_LOGIN_VIEW_STATE)
-                val mockCipherView = mockk<CipherView> {
-                    every { toViewState(isPremiumUser = true) } returns DEFAULT_LOGIN_VIEW_STATE
-                }
-                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
-
-                assertEquals(loginState, viewModel.stateFlow.value)
-                viewModel.trySendAction(
-                    VaultItemAction.Login.HiddenFieldVisibilityClicked(
-                        field = VaultItemState.ViewState.Content.Custom.HiddenField(
-                            name = "hidden",
-                            value = "value",
-                            isCopyable = true,
-                            isVisible = false,
-                        ),
-                        isVisible = true,
-                    ),
-                )
-                assertEquals(
-                    loginState.copy(dialog = VaultItemState.DialogState.MasterPasswordDialog),
-                    viewModel.stateFlow.value,
-                )
-
-                verify(exactly = 1) {
-                    mockCipherView.toViewState(isPremiumUser = true)
-                }
-            }
-
-        @Suppress("MaxLineLength")
-        @Test
-        fun `on HiddenFieldVisibilityClicked should update hidden field visibility when re-prompt is not required`() =
-            runTest {
-                val hiddenField = VaultItemState.ViewState.Content.Custom.HiddenField(
-                    name = "hidden",
-                    value = "value",
-                    isCopyable = true,
-                    isVisible = false,
-                )
-                val loginViewState = DEFAULT_EMPTY_LOGIN_VIEW_STATE.copy(
-                    requiresReprompt = false,
-                    customFields = listOf(hiddenField),
+                val loginViewState = createViewState(
+                    common = DEFAULT_COMMON.copy(requiresReprompt = false),
                 )
                 val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
                 val mockCipherView = mockk<CipherView> {
@@ -539,15 +586,17 @@ class VaultItemViewModelTest : BaseViewModelTest() {
 
                 assertEquals(loginState, viewModel.stateFlow.value)
                 viewModel.trySendAction(
-                    VaultItemAction.Login.HiddenFieldVisibilityClicked(
-                        field = hiddenField,
-                        isVisible = true,
+                    VaultItemAction.ItemType.Login.PasswordVisibilityClicked(
+                        true,
                     ),
                 )
                 assertEquals(
                     loginState.copy(
                         viewState = loginViewState.copy(
-                            customFields = listOf(hiddenField.copy(isVisible = true)),
+                            common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                            type = DEFAULT_LOGIN_TYPE.copy(
+                                passwordData = DEFAULT_LOGIN_TYPE.passwordData!!.copy(isVisible = true),
+                            ),
                         ),
                     ),
                     viewModel.stateFlow.value,
@@ -572,82 +621,104 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         authRepository = authRepository,
         vaultRepository = vaultRepository,
     )
+
+    private fun createViewState(
+        common: VaultItemState.ViewState.Content.Common = DEFAULT_COMMON,
+        type: VaultItemState.ViewState.Content.ItemType = DEFAULT_LOGIN_TYPE,
+    ): VaultItemState.ViewState.Content =
+        VaultItemState.ViewState.Content(
+            common = common,
+            type = type,
+        )
+
+    companion object {
+        private const val CIPHER_VIEW_EXTENSIONS_PATH: String =
+            "com.x8bit.bitwarden.ui.vault.feature.item.util.CipherViewExtensionsKt"
+
+        private const val VAULT_ITEM_ID = "vault_item_id"
+        private const val DEFAULT_LOGIN_PASSWORD = "password"
+        private const val DEFAULT_LOGIN_USERNAME = "username"
+
+        private val DEFAULT_STATE: VaultItemState = VaultItemState(
+            vaultItemId = VAULT_ITEM_ID,
+            viewState = VaultItemState.ViewState.Loading,
+            dialog = null,
+        )
+
+        private val DEFAULT_USER_STATE: UserState = UserState(
+            activeUserId = "user_id_1",
+            accounts = listOf(
+                UserState.Account(
+                    userId = "user_id_1",
+                    name = "Bit",
+                    email = "bitwarden@gmail.com",
+                    avatarColorHex = "#ff00ff",
+                    environment = Environment.Us,
+                    isPremium = true,
+                    isVaultUnlocked = true,
+                    organizations = emptyList(),
+                ),
+            ),
+        )
+
+        private val DEFAULT_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
+            VaultItemState.ViewState.Content.ItemType.Login(
+                passwordHistoryCount = 1,
+                username = DEFAULT_LOGIN_USERNAME,
+                passwordData = VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
+                    password = DEFAULT_LOGIN_PASSWORD,
+                    isVisible = false,
+                ),
+                uris = listOf(
+                    VaultItemState.ViewState.Content.ItemType.Login.UriData(
+                        uri = "www.example.com",
+                        isCopyable = true,
+                        isLaunchable = true,
+                    ),
+                ),
+                passwordRevisionDate = "12/31/69 06:16 PM",
+                totp = "otpauth://totp/Example:alice@google.com" +
+                    "?secret=JBSWY3DPEHPK3PXP&issuer=Example",
+            )
+
+        private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
+            VaultItemState.ViewState.Content.Common(
+                name = "login cipher",
+                lastUpdated = "12/31/69 06:16 PM",
+                notes = "Lots of notes",
+                isPremiumUser = true,
+                customFields = listOf(
+                    VaultItemState.ViewState.Content.Common.Custom.TextField(
+                        name = "text",
+                        value = "value",
+                        isCopyable = true,
+                    ),
+                    VaultItemState.ViewState.Content.Common.Custom.HiddenField(
+                        name = "hidden",
+                        value = "value",
+                        isCopyable = true,
+                        isVisible = false,
+                    ),
+                    VaultItemState.ViewState.Content.Common.Custom.BooleanField(
+                        name = "boolean",
+                        value = true,
+                    ),
+                    VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                        name = "linked username",
+                        vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
+                    ),
+                    VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                        name = "linked password",
+                        vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
+                    ),
+                ),
+                requiresReprompt = true,
+            )
+
+        private val DEFAULT_VIEW_STATE: VaultItemState.ViewState.Content =
+            VaultItemState.ViewState.Content(
+                common = DEFAULT_COMMON,
+                type = DEFAULT_LOGIN_TYPE,
+            )
+    }
 }
-
-private const val CIPHER_VIEW_EXTENSIONS_PATH: String =
-    "com.x8bit.bitwarden.ui.vault.feature.item.util.CipherViewExtensionsKt"
-
-private const val VAULT_ITEM_ID = "vault_item_id"
-private const val DEFAULT_LOGIN_PASSWORD = "password"
-private const val DEFAULT_LOGIN_USERNAME = "username"
-
-private val DEFAULT_STATE: VaultItemState = VaultItemState(
-    vaultItemId = VAULT_ITEM_ID,
-    viewState = VaultItemState.ViewState.Loading,
-    dialog = null,
-)
-
-private val DEFAULT_USER_STATE: UserState = UserState(
-    activeUserId = "user_id_1",
-    accounts = listOf(
-        UserState.Account(
-            userId = "user_id_1",
-            name = "Bit",
-            email = "bitwarden@gmail.com",
-            avatarColorHex = "#ff00ff",
-            environment = Environment.Us,
-            isPremium = true,
-            isVaultUnlocked = true,
-            organizations = emptyList(),
-        ),
-    ),
-)
-
-private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
-    VaultItemState.ViewState.Content.Login(
-        name = "login cipher",
-        lastUpdated = "12/31/69 06:16 PM",
-        passwordHistoryCount = 1,
-        notes = "Lots of notes",
-        isPremiumUser = true,
-        customFields = listOf(
-            VaultItemState.ViewState.Content.Custom.TextField(
-                name = "text",
-                value = "value",
-                isCopyable = true,
-            ),
-            VaultItemState.ViewState.Content.Custom.HiddenField(
-                name = "hidden",
-                value = "value",
-                isCopyable = true,
-                isVisible = false,
-            ),
-            VaultItemState.ViewState.Content.Custom.BooleanField(
-                name = "boolean",
-                value = true,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked username",
-                vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked password",
-                vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
-            ),
-        ),
-        requiresReprompt = true,
-        username = DEFAULT_LOGIN_USERNAME,
-        passwordData = VaultItemState.ViewState.Content.PasswordData(
-            password = DEFAULT_LOGIN_PASSWORD,
-            isVisible = false,
-        ),
-        uris = listOf(
-            VaultItemState.ViewState.Content.UriData(
-                uri = "www.example.com",
-                isCopyable = true,
-                isLaunchable = true,
-            ),
-        ),
-        passwordRevisionDate = "12/31/69 06:16 PM",
-        totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
-    )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
index 4edf8a713..23605eaa7 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
@@ -44,7 +44,12 @@ class CipherViewExtensionsTest {
         val isPremiumUser = false
         val viewState = DEFAULT_FULL_LOGIN_CIPHER_VIEW.toViewState(isPremiumUser = isPremiumUser)
 
-        assertEquals(DEFAULT_FULL_LOGIN_VIEW_STATE.copy(isPremiumUser = isPremiumUser), viewState)
+        assertEquals(
+            DEFAULT_FULL_LOGIN_VIEW_STATE.copy(
+                common = DEFAULT_FULL_LOGIN_VIEW_STATE.common.copy(isPremiumUser = isPremiumUser),
+            ),
+            viewState,
+        )
     }
 
     @Test
@@ -168,67 +173,77 @@ val DEFAULT_EMPTY_LOGIN_CIPHER_VIEW: CipherView = CipherView(
     revisionDate = Instant.ofEpochSecond(1_000L),
 )
 
-val DEFAULT_FULL_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
-    VaultItemState.ViewState.Content.Login(
-        name = "login cipher",
-        lastUpdated = "1/1/70 12:16 AM",
-        passwordHistoryCount = 1,
-        notes = "Lots of notes",
-        isPremiumUser = true,
-        customFields = listOf(
-            VaultItemState.ViewState.Content.Custom.TextField(
-                name = "text",
-                value = "value",
-                isCopyable = true,
+val DEFAULT_FULL_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content =
+    VaultItemState.ViewState.Content(
+        common = VaultItemState.ViewState.Content.Common(
+            name = "login cipher",
+            lastUpdated = "1/1/70 12:16 AM",
+            notes = "Lots of notes",
+            isPremiumUser = true,
+            customFields = listOf(
+                VaultItemState.ViewState.Content.Common.Custom.TextField(
+                    name = "text",
+                    value = "value",
+                    isCopyable = true,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.HiddenField(
+                    name = "hidden",
+                    value = "value",
+                    isCopyable = true,
+                    isVisible = false,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.BooleanField(
+                    name = "boolean",
+                    value = true,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                    name = "linked username",
+                    vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
+                ),
+                VaultItemState.ViewState.Content.Common.Custom.LinkedField(
+                    name = "linked password",
+                    vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
+                ),
             ),
-            VaultItemState.ViewState.Content.Custom.HiddenField(
-                name = "hidden",
-                value = "value",
-                isCopyable = true,
+            requiresReprompt = true,
+        ),
+        type = VaultItemState.ViewState.Content.ItemType.Login(
+            passwordHistoryCount = 1,
+            username = "username",
+            passwordData = VaultItemState.ViewState.Content.ItemType.Login.PasswordData(
+                password = "password",
                 isVisible = false,
             ),
-            VaultItemState.ViewState.Content.Custom.BooleanField(
-                name = "boolean",
-                value = true,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked username",
-                vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
-            ),
-            VaultItemState.ViewState.Content.Custom.LinkedField(
-                name = "linked password",
-                vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
+            uris = listOf(
+                VaultItemState.ViewState.Content.ItemType.Login.UriData(
+                    uri = "www.example.com",
+                    isCopyable = true,
+                    isLaunchable = true,
+                ),
             ),
+            passwordRevisionDate = "1/1/70 12:16 AM",
+            totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
         ),
-        requiresReprompt = true,
-        username = "username",
-        passwordData = VaultItemState.ViewState.Content.PasswordData(
-            password = "password",
-            isVisible = false,
-        ),
-        uris = listOf(
-            VaultItemState.ViewState.Content.UriData(
-                uri = "www.example.com",
-                isCopyable = true,
-                isLaunchable = true,
-            ),
-        ),
-        passwordRevisionDate = "1/1/70 12:16 AM",
-        totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
     )
 
-val DEFAULT_EMPTY_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
-    VaultItemState.ViewState.Content.Login(
-        name = "login cipher",
-        lastUpdated = "1/1/70 12:16 AM",
-        passwordHistoryCount = null,
-        notes = null,
-        isPremiumUser = true,
-        customFields = emptyList(),
-        requiresReprompt = true,
-        username = null,
-        passwordData = null,
-        uris = emptyList(),
-        passwordRevisionDate = null,
-        totp = null,
+val DEFAULT_EMPTY_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content =
+    VaultItemState.ViewState.Content(
+        common = VaultItemState.ViewState.Content.Common(
+            name = "login cipher",
+            lastUpdated = "1/1/70 12:16 AM",
+
+            notes = null,
+            isPremiumUser = true,
+            customFields = emptyList(),
+            requiresReprompt = true,
+
+            ),
+        type = VaultItemState.ViewState.Content.ItemType.Login(
+            passwordHistoryCount = null,
+            username = null,
+            passwordData = null,
+            uris = emptyList(),
+            passwordRevisionDate = null,
+            totp = null,
+        ),
     )