11package to.bitkit.ui.sheets
22
3- import androidx.compose.foundation.Image
4- import androidx.compose.foundation.layout.Arrangement
5- import androidx.compose.foundation.layout.Box
6- import androidx.compose.foundation.layout.BoxWithConstraints
73import androidx.compose.foundation.layout.Column
8- import androidx.compose.foundation.layout.Row
9- import androidx.compose.foundation.layout.fillMaxSize
104import androidx.compose.foundation.layout.fillMaxWidth
11- import androidx.compose.foundation.layout.navigationBarsPadding
12- import androidx.compose.foundation.layout.offset
13- import androidx.compose.foundation.layout.padding
14- import androidx.compose.foundation.layout.size
15- import androidx.compose.foundation.layout.width
165import androidx.compose.runtime.Composable
17- import androidx.compose.runtime.DisposableEffect
18- import androidx.compose.runtime.getValue
19- import androidx.compose.runtime.mutableStateOf
20- import androidx.compose.runtime.remember
21- import androidx.compose.runtime.setValue
22- import androidx.compose.ui.Alignment
236import androidx.compose.ui.Modifier
247import androidx.compose.ui.platform.testTag
25- import androidx.compose.ui.res.painterResource
26- import androidx.compose.ui.res.stringResource
27- import androidx.compose.ui.tooling.preview.Preview
28- import androidx.compose.ui.unit.dp
298import androidx.navigation.compose.NavHost
309import androidx.navigation.compose.rememberNavController
3110import kotlinx.serialization.Serializable
32- import to.bitkit.R
33- import to.bitkit.ui.components.BodyM
34- import to.bitkit.ui.components.BottomSheetPreview
35- import to.bitkit.ui.components.Display
36- import to.bitkit.ui.components.FillHeight
37- import to.bitkit.ui.components.KEY_DELETE
38- import to.bitkit.ui.components.NumberPad
39- import to.bitkit.ui.components.PrimaryButton
40- import to.bitkit.ui.components.SecondaryButton
4111import to.bitkit.ui.components.Sheet
4212import to.bitkit.ui.components.SheetSize
43- import to.bitkit.ui.components.VerticalSpacer
44- import to.bitkit.ui.scaffold.SheetTopBar
4513import to.bitkit.ui.shared.modifiers.sheetHeight
46- import to.bitkit.ui.shared.util.gradientBackground
47- import to.bitkit.ui.theme.AppThemeSurface
48- import to.bitkit.ui.theme.Colors
4914import to.bitkit.ui.utils.composableWithDefaultTransitions
50- import to.bitkit.ui.utils.withAccent
5115
5216/* *
5317 * Entry point for the hardware-wallet connect flow opened from the home suggestion
@@ -68,18 +32,17 @@ fun HardwareSheet(
6832 modifier = Modifier
6933 .fillMaxWidth()
7034 .sheetHeight(SheetSize .LARGE )
71- .gradientBackground()
7235 .testTag(" hardware_sheet" )
7336 ) {
7437 NavHost (
7538 navController = navController,
7639 startDestination = sheet.route,
7740 ) {
7841 composableWithDefaultTransitions<HardwareRoute .Intro > {
79- HardwareIntro (onClose = onDismiss)
42+ HwIntroSheet ( onDismiss)
8043 }
8144 composableWithDefaultTransitions<HardwareRoute .PairingCode > {
82- HardwarePairing (
45+ HwPairSheet (
8346 onSubmit = onSubmitPairingCode,
8447 onCancel = onCancelPairingCode,
8548 )
@@ -88,173 +51,10 @@ fun HardwareSheet(
8851 }
8952}
9053
91- @Composable
92- private fun HardwareIntro (onClose : () -> Unit ) {
93- Column (modifier = Modifier .fillMaxSize()) {
94- SheetTopBar (titleText = stringResource(R .string.hardware__intro_title))
95- BoxWithConstraints (
96- modifier = Modifier
97- .fillMaxWidth()
98- .weight(1f )
99- ) {
100- val imageSize = maxWidth * INTRO_IMAGE_SIZE_RATIO
101- val staggerY = maxWidth * INTRO_IMAGE_STAGGER_RATIO
102- Image (
103- painter = painterResource(R .drawable.trezor),
104- contentDescription = null ,
105- modifier = Modifier
106- .size(imageSize)
107- .align(Alignment .CenterStart )
108- .offset(x = - maxWidth * INTRO_TREZOR_BLEED_RATIO , y = staggerY)
109- )
110- Image (
111- painter = painterResource(R .drawable.ledger),
112- contentDescription = null ,
113- modifier = Modifier
114- .size(imageSize)
115- .align(Alignment .CenterEnd )
116- .offset(x = maxWidth * INTRO_LEDGER_BLEED_RATIO , y = - staggerY)
117- )
118- }
119- Column (
120- modifier = Modifier
121- .fillMaxWidth()
122- .padding(horizontal = 32 .dp)
123- .navigationBarsPadding()
124- ) {
125- Display (stringResource(R .string.hardware__intro_header).withAccent(accentColor = Colors .Blue ))
126- VerticalSpacer (8 .dp)
127- BodyM (stringResource(R .string.hardware__intro_text), color = Colors .White64 )
128- VerticalSpacer (32 .dp)
129- Row (
130- horizontalArrangement = Arrangement .spacedBy(16 .dp),
131- modifier = Modifier .fillMaxWidth(),
132- ) {
133- SecondaryButton (
134- text = stringResource(R .string.common__cancel),
135- onClick = onClose,
136- modifier = Modifier .weight(1f )
137- )
138- PrimaryButton (
139- text = stringResource(R .string.common__continue),
140- onClick = {},
141- enabled = false ,
142- modifier = Modifier .weight(1f )
143- )
144- }
145- VerticalSpacer (16 .dp)
146- }
147- }
148- }
149-
150- @Composable
151- private fun HardwarePairing (
152- onSubmit : (String ) -> Unit ,
153- onCancel : () -> Unit ,
154- ) {
155- var code by remember { mutableStateOf(" " ) }
156- var submitted by remember { mutableStateOf(false ) }
157-
158- // Dismissing the sheet without submitting (e.g. swipe down) cancels the pending
159- // pairing request so the connect attempt does not hang until its timeout.
160- DisposableEffect (Unit ) {
161- onDispose { if (! submitted) onCancel() }
162- }
163-
164- Column (modifier = Modifier .fillMaxSize()) {
165- SheetTopBar (titleText = stringResource(R .string.hardware__pairing_title))
166- Column (
167- horizontalAlignment = Alignment .CenterHorizontally ,
168- modifier = Modifier
169- .fillMaxWidth()
170- .weight(1f )
171- .padding(horizontal = 32 .dp)
172- ) {
173- BodyM (stringResource(R .string.hardware__pairing_text), color = Colors .White64 )
174- FillHeight ()
175- // Fixed-width cells so digits replace dots without the row shifting.
176- Row (horizontalArrangement = Arrangement .spacedBy(8 .dp)) {
177- repeat(PAIRING_CODE_LENGTH ) { index ->
178- val digit = code.getOrNull(index)?.toString()
179- Box (
180- contentAlignment = Alignment .Center ,
181- modifier = Modifier .width(PAIRING_CELL_WIDTH )
182- ) {
183- Display (
184- text = digit ? : " •" ,
185- color = if (digit != null ) Colors .White else Colors .White32 ,
186- )
187- }
188- }
189- }
190- FillHeight ()
191- }
192- NumberPad (
193- onPress = { key ->
194- when {
195- key == KEY_DELETE -> code = code.dropLast(1 )
196- code.length < PAIRING_CODE_LENGTH -> {
197- code + = key
198- if (code.length == PAIRING_CODE_LENGTH ) {
199- submitted = true
200- onSubmit(code)
201- }
202- }
203- }
204- },
205- includeNavigationBarsPadding = true ,
206- )
207- }
208- }
209-
21054sealed interface HardwareRoute {
21155 @Serializable
21256 data object Intro : HardwareRoute
21357
21458 @Serializable
21559 data object PairingCode : HardwareRoute
21660}
217-
218- private const val PAIRING_CODE_LENGTH = 6
219- private val PAIRING_CELL_WIDTH = 32 .dp
220-
221- // Proportions taken from the 375dp-wide Figma frame: 256dp visuals bleeding
222- // 84dp off the left edge and 53dp off the right, staggered by 12dp vertically.
223- private const val INTRO_IMAGE_SIZE_RATIO = 256f / 375f
224- private const val INTRO_TREZOR_BLEED_RATIO = 84f / 375f
225- private const val INTRO_LEDGER_BLEED_RATIO = 53f / 375f
226- private const val INTRO_IMAGE_STAGGER_RATIO = 12f / 375f
227-
228- @Preview(showSystemUi = true )
229- @Composable
230- private fun Preview () {
231- AppThemeSurface {
232- BottomSheetPreview {
233- Column (
234- modifier = Modifier
235- .fillMaxWidth()
236- .sheetHeight(SheetSize .LARGE , isModal = true )
237- .gradientBackground()
238- ) {
239- HardwareIntro (onClose = {})
240- }
241- }
242- }
243- }
244-
245- @Preview(showSystemUi = true )
246- @Composable
247- private fun PairingPreview () {
248- AppThemeSurface {
249- BottomSheetPreview {
250- Column (
251- modifier = Modifier
252- .fillMaxWidth()
253- .sheetHeight(SheetSize .LARGE , isModal = true )
254- .gradientBackground()
255- ) {
256- HardwarePairing (onSubmit = {}, onCancel = {})
257- }
258- }
259- }
260- }
0 commit comments