Skip to content

Commit d09945d

Browse files
SaintPatrckclaude
andauthored
[PM-29297] Add MigrateToMyItemsScreen (#6239)
Co-authored-by: Claude <[email protected]>
1 parent 30ce512 commit d09945d

File tree

8 files changed

+1037
-0
lines changed

8 files changed

+1037
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@file:OmitFromCoverage
2+
3+
package com.x8bit.bitwarden.ui.vault.feature.migratetomyitems
4+
5+
import androidx.lifecycle.SavedStateHandle
6+
import androidx.navigation.NavController
7+
import androidx.navigation.NavGraphBuilder
8+
import androidx.navigation.NavOptions
9+
import androidx.navigation.toRoute
10+
import com.bitwarden.annotation.OmitFromCoverage
11+
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
12+
import kotlinx.serialization.Serializable
13+
14+
/**
15+
* The type-safe route for the migrate to my items screen.
16+
*
17+
* @property organizationId The ID of the organization requiring migration.
18+
* @property organizationName The name of the organization requiring migration.
19+
*/
20+
@OmitFromCoverage
21+
@Serializable
22+
data class MigrateToMyItemsRoute(
23+
val organizationId: String,
24+
val organizationName: String,
25+
)
26+
27+
/**
28+
* Class to retrieve migrate to my items arguments from the [SavedStateHandle].
29+
*
30+
* @property organizationId The ID of the organization requiring migration.
31+
* @property organizationName The name of the organization requiring migration.
32+
*/
33+
data class MigrateToMyItemsArgs(
34+
val organizationId: String,
35+
val organizationName: String,
36+
)
37+
38+
/**
39+
* Constructs a [MigrateToMyItemsArgs] from the [SavedStateHandle] and internal route data.
40+
*/
41+
fun SavedStateHandle.toMigrateToMyItemsArgs(): MigrateToMyItemsArgs {
42+
val route = this.toRoute<MigrateToMyItemsRoute>()
43+
return MigrateToMyItemsArgs(
44+
organizationId = route.organizationId,
45+
organizationName = route.organizationName,
46+
)
47+
}
48+
49+
/**
50+
* Navigate to the migrate to my items screen.
51+
*
52+
* @param organizationId The ID of the organization requiring migration.
53+
* @param organizationName The name of the organization requiring migration.
54+
*/
55+
fun NavController.navigateToMigrateToMyItems(
56+
organizationId: String,
57+
organizationName: String,
58+
navOptions: NavOptions? = null,
59+
) {
60+
this.navigate(
61+
route = MigrateToMyItemsRoute(
62+
organizationId = organizationId,
63+
organizationName = organizationName,
64+
),
65+
navOptions = navOptions,
66+
)
67+
}
68+
69+
/**
70+
* Add the migrate to my items screen to the nav graph.
71+
*/
72+
fun NavGraphBuilder.migrateToMyItemsDestination(
73+
onNavigateToVault: () -> Unit,
74+
onNavigateToLeaveOrganization: () -> Unit,
75+
) {
76+
composableWithSlideTransitions<MigrateToMyItemsRoute> {
77+
MigrateToMyItemsScreen(
78+
onNavigateToVault = onNavigateToVault,
79+
onNavigateToLeaveOrganization = onNavigateToLeaveOrganization,
80+
)
81+
}
82+
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package com.x8bit.bitwarden.ui.vault.feature.migratetomyitems
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Spacer
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.navigationBarsPadding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.rememberScrollState
12+
import androidx.compose.foundation.verticalScroll
13+
import androidx.compose.material3.Text
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.getValue
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.layout.ContentScale
19+
import androidx.compose.ui.res.stringResource
20+
import androidx.compose.ui.text.style.TextAlign
21+
import androidx.compose.ui.tooling.preview.Preview
22+
import androidx.compose.ui.unit.dp
23+
import androidx.core.net.toUri
24+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
25+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
26+
import com.bitwarden.ui.platform.base.util.EventsEffect
27+
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
28+
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
29+
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
30+
import com.bitwarden.ui.platform.components.button.BitwardenTextButton
31+
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
32+
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
33+
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
34+
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
35+
import com.bitwarden.ui.platform.composition.LocalIntentManager
36+
import com.bitwarden.ui.platform.manager.IntentManager
37+
import com.bitwarden.ui.platform.resource.BitwardenDrawable
38+
import com.bitwarden.ui.platform.resource.BitwardenString
39+
import com.bitwarden.ui.platform.theme.BitwardenTheme
40+
import com.x8bit.bitwarden.ui.vault.feature.migratetomyitems.handler.rememberMigrateToMyItemsHandler
41+
42+
/**
43+
* Top level screen component for the MigrateToMyItems screen.
44+
*/
45+
@Composable
46+
fun MigrateToMyItemsScreen(
47+
onNavigateToVault: () -> Unit,
48+
onNavigateToLeaveOrganization: () -> Unit,
49+
viewModel: MigrateToMyItemsViewModel = hiltViewModel(),
50+
intentManager: IntentManager = LocalIntentManager.current,
51+
) {
52+
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
53+
val handlers = rememberMigrateToMyItemsHandler(viewModel)
54+
55+
EventsEffect(viewModel = viewModel) { event ->
56+
when (event) {
57+
MigrateToMyItemsEvent.NavigateToVault -> onNavigateToVault()
58+
MigrateToMyItemsEvent.NavigateToLeaveOrganization -> onNavigateToLeaveOrganization()
59+
is MigrateToMyItemsEvent.LaunchUri -> intentManager.launchUri(event.uri.toUri())
60+
}
61+
}
62+
63+
MigrateToMyItemsDialogs(
64+
dialog = state.dialog,
65+
onDismissRequest = handlers.onDismissDialog,
66+
)
67+
68+
BitwardenScaffold {
69+
MigrateToMyItemsContent(
70+
state = state,
71+
onAcceptClick = handlers.onAcceptClick,
72+
onDeclineClick = handlers.onDeclineClick,
73+
onHelpClick = handlers.onHelpClick,
74+
modifier = Modifier
75+
.fillMaxSize()
76+
.verticalScroll(rememberScrollState()),
77+
)
78+
}
79+
}
80+
81+
@Composable
82+
private fun MigrateToMyItemsDialogs(
83+
dialog: MigrateToMyItemsState.DialogState?,
84+
onDismissRequest: () -> Unit,
85+
) {
86+
when (dialog) {
87+
is MigrateToMyItemsState.DialogState.Error -> {
88+
BitwardenBasicDialog(
89+
title = dialog.title(),
90+
message = dialog.message(),
91+
onDismissRequest = onDismissRequest,
92+
)
93+
}
94+
95+
is MigrateToMyItemsState.DialogState.Loading -> {
96+
BitwardenLoadingDialog(text = dialog.message())
97+
}
98+
99+
null -> Unit
100+
}
101+
}
102+
103+
@Composable
104+
private fun MigrateToMyItemsContent(
105+
state: MigrateToMyItemsState,
106+
onAcceptClick: () -> Unit,
107+
onDeclineClick: () -> Unit,
108+
onHelpClick: () -> Unit,
109+
modifier: Modifier = Modifier,
110+
) {
111+
Column(
112+
modifier = modifier,
113+
horizontalAlignment = Alignment.CenterHorizontally,
114+
) {
115+
Spacer(modifier = Modifier.height(32.dp))
116+
Image(
117+
painter = rememberVectorPainter(id = BitwardenDrawable.ill_migrate_to_my_items),
118+
contentDescription = null,
119+
contentScale = ContentScale.FillHeight,
120+
modifier = Modifier
121+
.standardHorizontalMargin()
122+
.size(100.dp),
123+
)
124+
Spacer(modifier = Modifier.height(24.dp))
125+
MigrateToMyItemsTextContent(organizationName = state.organizationName)
126+
Spacer(modifier = Modifier.height(24.dp))
127+
MigrateToMyItemsActions(
128+
onContinueClick = onAcceptClick,
129+
onDeclineClick = onDeclineClick,
130+
onHelpClick = onHelpClick,
131+
)
132+
Spacer(modifier = Modifier.navigationBarsPadding())
133+
}
134+
}
135+
136+
@Composable
137+
private fun MigrateToMyItemsTextContent(
138+
organizationName: String,
139+
modifier: Modifier = Modifier,
140+
) {
141+
Column(modifier = modifier) {
142+
Text(
143+
text = stringResource(
144+
id = BitwardenString.transfer_items_to_org,
145+
organizationName,
146+
),
147+
style = BitwardenTheme.typography.titleMedium,
148+
color = BitwardenTheme.colorScheme.text.primary,
149+
textAlign = TextAlign.Center,
150+
modifier = Modifier
151+
.fillMaxWidth()
152+
.standardHorizontalMargin(),
153+
)
154+
Spacer(modifier = Modifier.height(12.dp))
155+
Text(
156+
text = stringResource(
157+
id = BitwardenString.transfer_items_description,
158+
organizationName,
159+
),
160+
style = BitwardenTheme.typography.bodyMedium,
161+
color = BitwardenTheme.colorScheme.text.primary,
162+
textAlign = TextAlign.Center,
163+
modifier = Modifier
164+
.fillMaxWidth()
165+
.standardHorizontalMargin(),
166+
)
167+
}
168+
}
169+
170+
@Composable
171+
private fun MigrateToMyItemsActions(
172+
onContinueClick: () -> Unit,
173+
onDeclineClick: () -> Unit,
174+
onHelpClick: () -> Unit,
175+
modifier: Modifier = Modifier,
176+
) {
177+
Column(modifier = modifier) {
178+
BitwardenFilledButton(
179+
label = stringResource(id = BitwardenString.accept),
180+
onClick = onContinueClick,
181+
modifier = Modifier
182+
.fillMaxWidth()
183+
.standardHorizontalMargin(),
184+
)
185+
Spacer(modifier = Modifier.height(12.dp))
186+
BitwardenOutlinedButton(
187+
label = stringResource(id = BitwardenString.decline_and_leave),
188+
onClick = onDeclineClick,
189+
modifier = Modifier
190+
.fillMaxWidth()
191+
.standardHorizontalMargin(),
192+
)
193+
Spacer(modifier = Modifier.height(12.dp))
194+
BitwardenTextButton(
195+
label = stringResource(id = BitwardenString.why_am_i_seeing_this),
196+
onClick = onHelpClick,
197+
modifier = Modifier
198+
.fillMaxWidth()
199+
.standardHorizontalMargin(),
200+
)
201+
}
202+
}
203+
204+
@Preview(showBackground = true)
205+
@Composable
206+
private fun MigrateToMyItemsScreen_preview() {
207+
BitwardenTheme {
208+
BitwardenScaffold {
209+
MigrateToMyItemsContent(
210+
state = MigrateToMyItemsState(
211+
organizationId = "test-org-id",
212+
organizationName = "Bitwarden",
213+
dialog = null,
214+
),
215+
onAcceptClick = {},
216+
onDeclineClick = {},
217+
onHelpClick = {},
218+
)
219+
}
220+
}
221+
}

0 commit comments

Comments
 (0)