From 1262b702947676c837a1fef05ad1d2cc7d578e4f Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Fri, 9 May 2025 08:41:15 -0300 Subject: [PATCH 1/3] test: implement bg image --- .../wallets/receive/EditInvoiceScreen.kt | 283 ++++++++++-------- 1 file changed, 151 insertions(+), 132 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt index 3992e58b3..ffbeadd7b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt @@ -6,7 +6,9 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow @@ -17,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -31,6 +32,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -59,7 +61,6 @@ import to.bitkit.ui.theme.AppTextFieldDefaults import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.CurrencyUiState -import to.bitkit.viewmodels.MainUiState @Composable fun EditInvoiceScreen( @@ -121,165 +122,183 @@ fun EditInvoiceContent( onClickTag: (String) -> Unit, onInputChanged: (String) -> Unit, ) { - Column( - modifier = Modifier - .fillMaxSize() - .gradientBackground() - .navigationBarsPadding() - .testTag("edit_invoice_screen") + Box( + modifier = Modifier.fillMaxWidth().gradientBackground() ) { - SheetTopBar(stringResource(R.string.wallet__receive_specify)) { - onBack() + + AnimatedVisibility(!keyboardVisible, modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomEnd) + ) { + Image( + painter = painterResource(R.drawable.coin_stack), + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .padding(32.dp) + ) } Column( modifier = Modifier - .padding(horizontal = 16.dp) - .testTag("edit_invoice_content") + .fillMaxSize() + .navigationBarsPadding() + .testTag("edit_invoice_screen") ) { - Spacer(Modifier.height(32.dp)) + SheetTopBar(stringResource(R.string.wallet__receive_specify)) { + onBack() + } - NumberPadTextField( - input = input, - displayUnit = displayUnit, - primaryDisplay = primaryDisplay, + Column( modifier = Modifier - .fillMaxWidth() - .clickableAlpha(onClick = onClickBalance) - .testTag("amount_input_field") - ) - - // Animated visibility for keyboard section - AnimatedVisibility( - visible = keyboardVisible, - enter = slideInVertically( - initialOffsetY = { fullHeight -> fullHeight }, - animationSpec = tween(durationMillis = 300) - ) + fadeIn(), - exit = slideOutVertically( - targetOffsetY = { fullHeight -> fullHeight }, - animationSpec = tween(durationMillis = 300) - ) + fadeOut() + .padding(horizontal = 16.dp) + .testTag("edit_invoice_content") ) { - Column( - modifier = Modifier.testTag("keyboard_section") - ) { - Spacer(modifier = Modifier.weight(1f)) + Spacer(Modifier.height(32.dp)) + + NumberPadTextField( + input = input, + displayUnit = displayUnit, + primaryDisplay = primaryDisplay, + modifier = Modifier + .fillMaxWidth() + .clickableAlpha(onClick = onClickBalance) + .testTag("amount_input_field") + ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, - modifier = Modifier.fillMaxWidth() + // Animated visibility for keyboard section + AnimatedVisibility( + visible = keyboardVisible, + enter = slideInVertically( + initialOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300) + ) + fadeIn(), + exit = slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight }, + animationSpec = tween(durationMillis = 300) + ) + fadeOut() + ) { + Column( + modifier = Modifier.testTag("keyboard_section") ) { - UnitButton(modifier = Modifier.height(28.dp)) - } + Spacer(modifier = Modifier.weight(1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth() + ) { + UnitButton(modifier = Modifier.height(28.dp)) + } - HorizontalDivider(modifier = Modifier.padding(vertical = 24.dp)) + HorizontalDivider(modifier = Modifier.padding(vertical = 24.dp)) - Keyboard( - onClick = { number -> - onInputChanged(if (input == "0") number else input + number) - }, - onClickBackspace = { - onInputChanged(if (input.length > 1) input.dropLast(1) else "0") - }, - isDecimal = primaryDisplay == PrimaryDisplay.FIAT, - modifier = Modifier - .fillMaxWidth() - .testTag("amount_keyboard"), - ) + Keyboard( + onClick = { number -> + onInputChanged(if (input == "0") number else input + number) + }, + onClickBackspace = { + onInputChanged(if (input.length > 1) input.dropLast(1) else "0") + }, + isDecimal = primaryDisplay == PrimaryDisplay.FIAT, + modifier = Modifier + .fillMaxWidth() + .testTag("amount_keyboard"), + ) - Spacer(modifier = Modifier.height(41.dp)) + Spacer(modifier = Modifier.height(41.dp)) - PrimaryButton( - text = stringResource(R.string.common__continue), - onClick = onContinueKeyboard, - modifier = Modifier.testTag("keyboard_continue_button") - ) + PrimaryButton( + text = stringResource(R.string.common__continue), + onClick = onContinueKeyboard, + modifier = Modifier.testTag("keyboard_continue_button") + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) + } } - } - // Animated visibility for note section - AnimatedVisibility( - visible = !keyboardVisible, - enter = fadeIn(animationSpec = tween(durationMillis = 300)), - exit = fadeOut(animationSpec = tween(durationMillis = 300)) - ) { - Column( - modifier = Modifier.testTag("note_section") + // Animated visibility for note section + AnimatedVisibility( + visible = !keyboardVisible, + enter = fadeIn(animationSpec = tween(durationMillis = 300)), + exit = fadeOut(animationSpec = tween(durationMillis = 300)) ) { - Spacer(modifier = Modifier.height(44.dp)) + Column( + modifier = Modifier.testTag("note_section") + ) { + Spacer(modifier = Modifier.height(44.dp)) - Caption13Up(text = stringResource(R.string.wallet__note), color = Colors.White64) + Caption13Up(text = stringResource(R.string.wallet__note), color = Colors.White64) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - TextField( - placeholder = { - BodySSB( - text = stringResource(R.string.wallet__receive_note_placeholder), - color = Colors.White64 - ) - }, - value = noteText, - onValueChange = onTextChanged, - minLines = 4, - keyboardOptions = KeyboardOptions.Default.copy( - imeAction = ImeAction.Done - ), - colors = AppTextFieldDefaults.semiTransparent, - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .fillMaxWidth() - .testTag("note_input_field") + TextField( + placeholder = { + BodySSB( + text = stringResource(R.string.wallet__receive_note_placeholder), + color = Colors.White64 + ) + }, + value = noteText, + onValueChange = onTextChanged, + minLines = 4, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done + ), + colors = AppTextFieldDefaults.semiTransparent, + shape = MaterialTheme.shapes.medium, + modifier = Modifier + .fillMaxWidth() + .testTag("note_input_field") - ) + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - Caption13Up(text = stringResource(R.string.wallet__tags), color = Colors.White64) - Spacer(modifier = Modifier.height(8.dp)) - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp) - ) { - tags.map { tagText -> - TagButton( - text = tagText, - isSelected = false, - displayIconClose = true, - onClick = { onClickTag(tagText) }, - ) + Caption13Up(text = stringResource(R.string.wallet__tags), color = Colors.White64) + Spacer(modifier = Modifier.height(8.dp)) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) { + tags.map { tagText -> + TagButton( + text = tagText, + isSelected = false, + displayIconClose = true, + onClick = { onClickTag(tagText) }, + ) + } } - } - PrimaryButton( - text = stringResource(R.string.wallet__tags_add), - size = ButtonSize.Small, - onClick = { onClickAddTag() }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_tag), - contentDescription = null, - tint = Colors.Brand - ) - }, - fullWidth = false - ) + PrimaryButton( + text = stringResource(R.string.wallet__tags_add), + size = ButtonSize.Small, + onClick = { onClickAddTag() }, + icon = { + Icon( + painter = painterResource(R.drawable.ic_tag), + contentDescription = null, + tint = Colors.Brand + ) + }, + fullWidth = false + ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) - PrimaryButton( - text = stringResource(R.string.wallet__receive_show_qr), - onClick = onContinueGeneral, - modifier = Modifier.testTag("general_continue_button") - ) + PrimaryButton( + text = stringResource(R.string.wallet__receive_show_qr), + onClick = onContinueGeneral, + modifier = Modifier.testTag("general_continue_button") + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) + } } } } From 17b4bf6a2470781a2d8686bb62e4f2ca93a116f2 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Fri, 9 May 2025 09:03:56 -0300 Subject: [PATCH 2/3] feat: fading animation --- .../wallets/receive/EditInvoiceScreen.kt | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt index ffbeadd7b..77ca4c9ef 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -33,12 +34,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import to.bitkit.R import to.bitkit.models.BitcoinDisplayUnit import to.bitkit.models.PrimaryDisplay @@ -76,6 +80,18 @@ fun EditInvoiceScreen( val currencyVM = currencyViewModel ?: return var satsString by rememberSaveable { mutableStateOf("") } var keyboardVisible by remember { mutableStateOf(false) } + var isSoftKeyboardVisible by remember { mutableStateOf(false) } + val view = LocalView.current + + LaunchedEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> + val isKeyboardNowVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) + if (isKeyboardNowVisible != isSoftKeyboardVisible) { + isSoftKeyboardVisible = isKeyboardNowVisible + } + insets + } + } AmountInputHandler( input = walletUiState.balanceInput, @@ -100,7 +116,8 @@ fun EditInvoiceScreen( onContinueKeyboard = { keyboardVisible = false }, onContinueGeneral = { updateInvoice(satsString.toULongOrNull()) }, onClickAddTag = onClickAddTag, - onClickTag = onClickTag + onClickTag = onClickTag, + isSoftKeyboardVisible = isSoftKeyboardVisible ) } @@ -109,6 +126,7 @@ fun EditInvoiceScreen( fun EditInvoiceContent( input: String, noteText: String, + isSoftKeyboardVisible: Boolean, keyboardVisible: Boolean, primaryDisplay: PrimaryDisplay, displayUnit: BitcoinDisplayUnit, @@ -123,12 +141,18 @@ fun EditInvoiceContent( onInputChanged: (String) -> Unit, ) { Box( - modifier = Modifier.fillMaxWidth().gradientBackground() + modifier = Modifier + .fillMaxWidth() + .gradientBackground() ) { - AnimatedVisibility(!keyboardVisible, modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomEnd) + AnimatedVisibility( + visible = !keyboardVisible && !isSoftKeyboardVisible, + enter = fadeIn(), + exit = fadeOut(), + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomEnd) ) { Image( painter = painterResource(R.drawable.coin_stack), @@ -323,7 +347,8 @@ private fun Preview() { onContinueKeyboard = {}, tags = listOf(), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } @@ -346,7 +371,8 @@ private fun Preview2() { onContinueKeyboard = {}, tags = listOf("Team", "Dinner", "Home", "Work"), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } @@ -369,7 +395,8 @@ private fun Preview3() { onContinueKeyboard = {}, tags = listOf("Team", "Dinner"), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } From 3e7aab612e0b165f2d689e679c86009de55fae1d Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Fri, 9 May 2025 09:11:15 -0300 Subject: [PATCH 3/3] feat: move softKeyboard logic to a keyboardAsState method --- .../wallets/receive/EditInvoiceScreen.kt | 34 ++++++------------- .../main/java/to/bitkit/ui/utils/Keyboard.kt | 23 +++++++++++++ 2 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/utils/Keyboard.kt diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt index 77ca4c9ef..016e0e2f6 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextField import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -34,15 +33,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import to.bitkit.R import to.bitkit.models.BitcoinDisplayUnit import to.bitkit.models.PrimaryDisplay @@ -64,6 +60,7 @@ import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppTextFieldDefaults import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.keyboardAsState import to.bitkit.viewmodels.CurrencyUiState @Composable @@ -80,18 +77,7 @@ fun EditInvoiceScreen( val currencyVM = currencyViewModel ?: return var satsString by rememberSaveable { mutableStateOf("") } var keyboardVisible by remember { mutableStateOf(false) } - var isSoftKeyboardVisible by remember { mutableStateOf(false) } - val view = LocalView.current - - LaunchedEffect(view) { - ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> - val isKeyboardNowVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) - if (isKeyboardNowVisible != isSoftKeyboardVisible) { - isSoftKeyboardVisible = isKeyboardNowVisible - } - insets - } - } + var isSoftKeyboardVisible by keyboardAsState() AmountInputHandler( input = walletUiState.balanceInput, @@ -110,7 +96,7 @@ fun EditInvoiceScreen( tags = walletUiState.selectedTags, onBack = onBack, onTextChanged = onDescriptionUpdate, - keyboardVisible = keyboardVisible, + numericKeyboardVisible = keyboardVisible, onClickBalance = { keyboardVisible = true }, onInputChanged = onInputUpdated, onContinueKeyboard = { keyboardVisible = false }, @@ -127,7 +113,7 @@ fun EditInvoiceContent( input: String, noteText: String, isSoftKeyboardVisible: Boolean, - keyboardVisible: Boolean, + numericKeyboardVisible: Boolean, primaryDisplay: PrimaryDisplay, displayUnit: BitcoinDisplayUnit, tags: List, @@ -147,7 +133,7 @@ fun EditInvoiceContent( ) { AnimatedVisibility( - visible = !keyboardVisible && !isSoftKeyboardVisible, + visible = !numericKeyboardVisible && !isSoftKeyboardVisible, enter = fadeIn(), exit = fadeOut(), modifier = Modifier @@ -193,7 +179,7 @@ fun EditInvoiceContent( // Animated visibility for keyboard section AnimatedVisibility( - visible = keyboardVisible, + visible = numericKeyboardVisible, enter = slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, animationSpec = tween(durationMillis = 300) @@ -245,7 +231,7 @@ fun EditInvoiceContent( // Animated visibility for note section AnimatedVisibility( - visible = !keyboardVisible, + visible = !numericKeyboardVisible, enter = fadeIn(animationSpec = tween(durationMillis = 300)), exit = fadeOut(animationSpec = tween(durationMillis = 300)) ) { @@ -340,7 +326,7 @@ private fun Preview() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = false, + numericKeyboardVisible = false, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, @@ -364,7 +350,7 @@ private fun Preview2() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = false, + numericKeyboardVisible = false, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, @@ -388,7 +374,7 @@ private fun Preview3() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = true, + numericKeyboardVisible = true, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, diff --git a/app/src/main/java/to/bitkit/ui/utils/Keyboard.kt b/app/src/main/java/to/bitkit/ui/utils/Keyboard.kt new file mode 100644 index 000000000..d88933beb --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/utils/Keyboard.kt @@ -0,0 +1,23 @@ +package to.bitkit.ui.utils + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat + +@Composable +fun keyboardAsState(): MutableState { + val keyboardState = remember { mutableStateOf(false) } + val view = LocalView.current + LaunchedEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + keyboardState.value = insets.isVisible(WindowInsetsCompat.Type.ime()) + insets + } + } + return keyboardState +}