mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -06:00
Add VaultTakeoverScreen and ViewModel
Create the VaultTakeoverScreen composable and ViewModel following MVVM + UDF architecture:
- VaultTakeoverViewModel: Handles user actions (Continue, Decline, Help) and emits navigation events
- VaultTakeoverScreen: Full-screen informational UI with organization transfer messaging
- VaultTakeoverState: Holds organization name (stubbed with TODO)
- VaultTakeoverEvent: Navigation events for vault, leave org, and help URI
- VaultTakeoverAction: User action types
Screen UI includes:
- Placeholder illustration (using ic_bw_passkey temporarily)
- Title and description text with org name placeholders
- Continue button (primary action)
- Decline and leave button (secondary action)
- Help link ("Why am I seeing this?")
- Proper spacing and scrollable layout
- Extracted text and action components for code length compliance
Also fixed: Renamed 'continue' string resource to 'continue_label' to avoid Java keyword conflict.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e11f20c466
commit
430baa2ff8
@ -0,0 +1,192 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vaulttakeover
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenTextButton
|
||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.bitwarden.ui.platform.manager.IntentManager
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* Top level screen component for the VaultTakeover screen.
|
||||
*/
|
||||
@Composable
|
||||
fun VaultTakeoverScreen(
|
||||
onNavigateToVault: () -> Unit,
|
||||
onNavigateToLeaveOrganization: () -> Unit,
|
||||
viewModel: VaultTakeoverViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
VaultTakeoverEvent.NavigateToVault -> onNavigateToVault()
|
||||
VaultTakeoverEvent.NavigateToLeaveOrganization -> onNavigateToLeaveOrganization()
|
||||
is VaultTakeoverEvent.LaunchUri -> intentManager.launchUri(event.uri.toUri())
|
||||
}
|
||||
}
|
||||
|
||||
val onContinueClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultTakeoverAction.ContinueClicked) }
|
||||
}
|
||||
val onDeclineClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultTakeoverAction.DeclineAndLeaveClicked) }
|
||||
}
|
||||
val onHelpClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultTakeoverAction.HelpLinkClicked) }
|
||||
}
|
||||
|
||||
BitwardenScaffold {
|
||||
VaultTakeoverContent(
|
||||
organizationName = state.organizationName,
|
||||
onContinueClick = onContinueClick,
|
||||
onDeclineClick = onDeclineClick,
|
||||
onHelpClick = onHelpClick,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VaultTakeoverContent(
|
||||
organizationName: String,
|
||||
onContinueClick: () -> Unit,
|
||||
onDeclineClick: () -> Unit,
|
||||
onHelpClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Image(
|
||||
painter = painterResource(id = BitwardenDrawable.ic_bw_passkey),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
VaultTakeoverTextContent(organizationName = organizationName)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
VaultTakeoverActions(
|
||||
onContinueClick = onContinueClick,
|
||||
onDeclineClick = onDeclineClick,
|
||||
onHelpClick = onHelpClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VaultTakeoverTextContent(
|
||||
organizationName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = BitwardenString.transfer_items_to_org,
|
||||
organizationName,
|
||||
),
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = BitwardenString.transfer_items_description,
|
||||
organizationName,
|
||||
),
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VaultTakeoverActions(
|
||||
onContinueClick: () -> Unit,
|
||||
onDeclineClick: () -> Unit,
|
||||
onHelpClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = BitwardenString.continue_label),
|
||||
onClick = onContinueClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(id = BitwardenString.decline_and_leave),
|
||||
onClick = onDeclineClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = BitwardenString.why_am_i_seeing_this),
|
||||
onClick = onHelpClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun VaultTakeoverScreen_preview() {
|
||||
BitwardenTheme {
|
||||
VaultTakeoverContent(
|
||||
organizationName = "Test Organization",
|
||||
onContinueClick = {},
|
||||
onDeclineClick = {},
|
||||
onHelpClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vaulttakeover
|
||||
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* View model for the [VaultTakeoverScreen].
|
||||
*/
|
||||
@HiltViewModel
|
||||
class VaultTakeoverViewModel @Inject constructor(
|
||||
// TODO: Inject required repositories/managers
|
||||
) : BaseViewModel<VaultTakeoverState, VaultTakeoverEvent, VaultTakeoverAction>(
|
||||
initialState = VaultTakeoverState(
|
||||
organizationName = "TODO", // TODO: Get from navigation args or repository
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: VaultTakeoverAction) {
|
||||
when (action) {
|
||||
VaultTakeoverAction.ContinueClicked -> handleContinueClicked()
|
||||
VaultTakeoverAction.DeclineAndLeaveClicked -> handleDeclineAndLeaveClicked()
|
||||
VaultTakeoverAction.HelpLinkClicked -> handleHelpLinkClicked()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleContinueClicked() {
|
||||
sendEvent(VaultTakeoverEvent.NavigateToVault)
|
||||
}
|
||||
|
||||
private fun handleDeclineAndLeaveClicked() {
|
||||
sendEvent(VaultTakeoverEvent.NavigateToLeaveOrganization)
|
||||
}
|
||||
|
||||
private fun handleHelpLinkClicked() {
|
||||
sendEvent(VaultTakeoverEvent.LaunchUri("TODO_HELP_URL"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the state for the [VaultTakeoverScreen].
|
||||
*/
|
||||
data class VaultTakeoverState(
|
||||
val organizationName: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Models the events that can be sent from the [VaultTakeoverViewModel].
|
||||
*/
|
||||
sealed class VaultTakeoverEvent {
|
||||
/**
|
||||
* Navigate to the vault screen after accepting takeover.
|
||||
*/
|
||||
data object NavigateToVault : VaultTakeoverEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the leave organization flow after declining.
|
||||
*/
|
||||
data object NavigateToLeaveOrganization : VaultTakeoverEvent()
|
||||
|
||||
/**
|
||||
* Launch a URI in the browser or appropriate handler.
|
||||
*/
|
||||
data class LaunchUri(val uri: String) : VaultTakeoverEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the actions that can be handled by the [VaultTakeoverViewModel].
|
||||
*/
|
||||
sealed class VaultTakeoverAction {
|
||||
/**
|
||||
* User clicked the Continue button.
|
||||
*/
|
||||
data object ContinueClicked : VaultTakeoverAction()
|
||||
|
||||
/**
|
||||
* User clicked the Decline and Leave button.
|
||||
*/
|
||||
data object DeclineAndLeaveClicked : VaultTakeoverAction()
|
||||
|
||||
/**
|
||||
* User clicked the "Why am I seeing this?" help link.
|
||||
*/
|
||||
data object HelpLinkClicked : VaultTakeoverAction()
|
||||
}
|
||||
@ -1163,7 +1163,7 @@ Do you want to switch to this account?</string>
|
||||
<string name="resending">Resending</string>
|
||||
<string name="transfer_items_to_org">Transfer items to %1$s</string>
|
||||
<string name="transfer_items_description">%1$s is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.</string>
|
||||
<string name="continue">Continue</string>
|
||||
<string name="continue_label">Continue</string>
|
||||
<string name="decline_and_leave">Decline and leave</string>
|
||||
<string name="why_am_i_seeing_this">Why am I seeing this?</string>
|
||||
</resources>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user