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..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 @@ -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 @@ -58,8 +60,8 @@ 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 -import to.bitkit.viewmodels.MainUiState @Composable fun EditInvoiceScreen( @@ -75,6 +77,7 @@ fun EditInvoiceScreen( val currencyVM = currencyViewModel ?: return var satsString by rememberSaveable { mutableStateOf("") } var keyboardVisible by remember { mutableStateOf(false) } + var isSoftKeyboardVisible by keyboardAsState() AmountInputHandler( input = walletUiState.balanceInput, @@ -93,13 +96,14 @@ fun EditInvoiceScreen( tags = walletUiState.selectedTags, onBack = onBack, onTextChanged = onDescriptionUpdate, - keyboardVisible = keyboardVisible, + numericKeyboardVisible = keyboardVisible, onClickBalance = { keyboardVisible = true }, onInputChanged = onInputUpdated, onContinueKeyboard = { keyboardVisible = false }, onContinueGeneral = { updateInvoice(satsString.toULongOrNull()) }, onClickAddTag = onClickAddTag, - onClickTag = onClickTag + onClickTag = onClickTag, + isSoftKeyboardVisible = isSoftKeyboardVisible ) } @@ -108,7 +112,8 @@ fun EditInvoiceScreen( fun EditInvoiceContent( input: String, noteText: String, - keyboardVisible: Boolean, + isSoftKeyboardVisible: Boolean, + numericKeyboardVisible: Boolean, primaryDisplay: PrimaryDisplay, displayUnit: BitcoinDisplayUnit, tags: List, @@ -121,165 +126,189 @@ fun EditInvoiceContent( onClickTag: (String) -> Unit, onInputChanged: (String) -> Unit, ) { - Column( + Box( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .gradientBackground() - .navigationBarsPadding() - .testTag("edit_invoice_screen") ) { - SheetTopBar(stringResource(R.string.wallet__receive_specify)) { - onBack() - } - Column( + AnimatedVisibility( + visible = !numericKeyboardVisible && !isSoftKeyboardVisible, + enter = fadeIn(), + exit = fadeOut(), modifier = Modifier - .padding(horizontal = 16.dp) - .testTag("edit_invoice_content") + .fillMaxWidth() + .align(Alignment.BottomEnd) ) { - Spacer(Modifier.height(32.dp)) - - NumberPadTextField( - input = input, - displayUnit = displayUnit, - primaryDisplay = primaryDisplay, + Image( + painter = painterResource(R.drawable.coin_stack), + contentDescription = null, + contentScale = ContentScale.FillWidth, modifier = Modifier .fillMaxWidth() - .clickableAlpha(onClick = onClickBalance) - .testTag("amount_input_field") + .padding(32.dp) ) + } + + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .testTag("edit_invoice_screen") + ) { + SheetTopBar(stringResource(R.string.wallet__receive_specify)) { + onBack() + } - // 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 + .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 = numericKeyboardVisible, + 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 = !numericKeyboardVisible, + 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)) + } } } } @@ -297,14 +326,15 @@ private fun Preview() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = false, + numericKeyboardVisible = false, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, onContinueKeyboard = {}, tags = listOf(), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } @@ -320,14 +350,15 @@ private fun Preview2() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = false, + numericKeyboardVisible = false, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, onContinueKeyboard = {}, tags = listOf("Team", "Dinner", "Home", "Work"), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } @@ -343,14 +374,15 @@ private fun Preview3() { displayUnit = BitcoinDisplayUnit.MODERN, onBack = {}, onTextChanged = {}, - keyboardVisible = true, + numericKeyboardVisible = true, onClickBalance = {}, onInputChanged = {}, onContinueGeneral = {}, onContinueKeyboard = {}, tags = listOf("Team", "Dinner"), onClickAddTag = {}, - onClickTag = {} + onClickTag = {}, + isSoftKeyboardVisible = false, ) } } 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 +}