diff --git a/app/src/main/java/to/bitkit/models/Currency.kt b/app/src/main/java/to/bitkit/models/Currency.kt index 7718b310d..784b0499e 100644 --- a/app/src/main/java/to/bitkit/models/Currency.kt +++ b/app/src/main/java/to/bitkit/models/Currency.kt @@ -2,6 +2,7 @@ package to.bitkit.models import kotlinx.datetime.Instant import kotlinx.serialization.Serializable +import to.bitkit.models.ConvertedAmount.BitcoinDisplayComponents import java.math.BigDecimal import java.text.DecimalFormat import java.text.DecimalFormatSymbols @@ -79,6 +80,31 @@ data class ConvertedAmount( } } +/** Format sats to modern or classic format*/ +fun Long.formatSats(unit: BitcoinDisplayUnit = BitcoinDisplayUnit.MODERN): BitcoinDisplayComponents { + val symbol = "₿" + val spaceSeparator = ' ' + val formattedValue = when (unit) { + BitcoinDisplayUnit.MODERN -> { + this.formatToModernDisplay() + } + + BitcoinDisplayUnit.CLASSIC -> { + val formatSymbols = DecimalFormatSymbols(Locale.getDefault()).apply { + groupingSeparator = spaceSeparator + } + val formatter = DecimalFormat("#,###.########", formatSymbols) + val btcValue: BigDecimal = BigDecimal(this).divide(BigDecimal(100_000_000)) + formatter.format(btcValue) + } + } + return BitcoinDisplayComponents( + symbol = symbol, + value = formattedValue, + ) +} + + fun Long.formatToModernDisplay(): String { val sats = this val formatSymbols = DecimalFormatSymbols(Locale.getDefault()).apply { diff --git a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt index 54a9dcf17..38fe75e4c 100644 --- a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt +++ b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt @@ -3,18 +3,32 @@ package to.bitkit.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableDoubleStateOf +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.models.ConvertedAmount import to.bitkit.models.PrimaryDisplay +import to.bitkit.models.formatSats import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.currencyViewModel import to.bitkit.ui.shared.util.clickableAlpha +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.formatFiat @Composable fun BalanceHeaderView( @@ -27,97 +41,205 @@ fun BalanceHeaderView( val (rates, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current val converted: ConvertedAmount? = if (rates.isNotEmpty()) currency.convert(sats = sats) else null - Column( - verticalArrangement = Arrangement.spacedBy(4.dp), - horizontalAlignment = Alignment.Start, - modifier = modifier - .clickableAlpha { currency.togglePrimaryDisplay() } - ) { - converted?.let { converted -> - if (primaryDisplay == PrimaryDisplay.BITCOIN) { - Column { - SmallRow( - prefix = prefix, - text = "${converted.symbol} ${converted.formatted}" - ) - - // large row - val btcComponents = converted.bitcoinDisplay(displayUnit) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.height(62.dp) - ) { - if (prefix != null) { - Display( - text = prefix, - modifier = Modifier - .alpha(0.6f) - .padding(end = 8.dp) - ) - } - if (showBitcoinSymbol) { - Display( - text = btcComponents.symbol, - modifier = Modifier - .alpha(0.6f) - .padding(end = 8.dp) - ) - } - Display(text = btcComponents.value) - } + converted?.let { converted -> + val btcComponents = converted.bitcoinDisplay(displayUnit) + + if (primaryDisplay == PrimaryDisplay.BITCOIN) { + BalanceHeader( + modifier = modifier, + smallRowPrefix = prefix, + smallRowSymbol = converted.symbol, + smallRowText = converted.formatted, + largeRowPrefix = prefix, + largeRowText = btcComponents.value, + largeRowSymbol = btcComponents.symbol, + showSymbol = showBitcoinSymbol, + onClick = { currency.togglePrimaryDisplay() } + ) + } else { + BalanceHeader( + modifier = modifier, + smallRowPrefix = prefix, + smallRowSymbol = btcComponents.symbol, + smallRowText = btcComponents.value, + largeRowPrefix = prefix, + largeRowText = converted.formatted, + largeRowSymbol = converted.symbol, + showSymbol = true, + onClick = { currency.togglePrimaryDisplay() } + ) + } + } +} + + +@Composable +fun BalanceHeaderEditable( + input: String, + showBitcoinSymbol: Boolean = true, + onSatsChanged: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val (rates, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current + val currency = currencyViewModel ?: return + + var satsValue: Long by remember { mutableLongStateOf(0L) } + var fiatValue: Double by remember { mutableDoubleStateOf(0.0) } + + var smallRowPrefix: String by remember { mutableStateOf("") } + var smallRowText: String by remember { mutableStateOf("") } + var smallRowSymbol: String by remember { mutableStateOf("") } + + var largeRowPrefix: String by remember { mutableStateOf("") } + var largeRowText: String by remember { mutableStateOf("") } + var largeRowSymbol: String by remember { mutableStateOf("") } + + LaunchedEffect(input) { //TODO HANDLE PRIMARY DISPLAY CHANGE + when (primaryDisplay) { + PrimaryDisplay.BITCOIN -> { + + (satsValue.toString() + input).toLongOrNull()?.let { updatedValue -> + satsValue = updatedValue } - } else { - Column { - val btcComponents = converted.bitcoinDisplay(displayUnit) - SmallRow( - prefix = prefix, - text = "${btcComponents.symbol} ${btcComponents.value}" - ) - - // large row - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.height(62.dp) - ) { - if (prefix != null) { - Display( - text = prefix, - modifier = Modifier - .alpha(0.6f) - .padding(end = 8.dp) - ) - } - Display( - text = converted.symbol, - modifier = Modifier - .alpha(0.6f) - .padding(end = 8.dp) - ) - Display(text = converted.formatted) - } + + val converted = currency.convert(sats = satsValue) + converted?.let { + val btcComponents = satsValue.formatSats(displayUnit) + smallRowSymbol = converted.symbol + smallRowText = converted.formatted + largeRowSymbol = btcComponents.symbol + largeRowText = btcComponents.value + } + } + PrimaryDisplay.FIAT -> { + //CHECK IF HAS VALUE AFTER DOTS + (if (fiatValue == 0.0) input else (fiatValue.toString() + input)).toDoubleOrNull()?.let { updatedValue -> + fiatValue = updatedValue + } + val converted = currency.convertFiatToSats(fiatAmount = fiatValue) + converted?.let { + val btcValues = converted.formatSats() + satsValue = it + smallRowSymbol = btcValues.symbol + smallRowText = btcValues.value + + largeRowSymbol = "$" + largeRowText = fiatValue.toString() } } } + + onSatsChanged(satsValue.toString()) + } + + BalanceHeader( + modifier = modifier, + smallRowPrefix = smallRowPrefix, + smallRowSymbol = smallRowSymbol, + smallRowText = smallRowText, + largeRowPrefix = largeRowPrefix, + largeRowText = largeRowText, + largeRowSymbol = largeRowSymbol, + showSymbol = showBitcoinSymbol, + onClick = { } + ) +} + +@Composable +fun BalanceHeader( + smallRowPrefix: String? = null, + smallRowSymbol: String? = null, + smallRowText: String, + largeRowPrefix: String? = null, + largeRowText: String, + largeRowSymbol: String, + showSymbol: Boolean, + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start, + modifier = modifier.clickableAlpha { onClick() } + ) { + SmallRow( + prefix = smallRowPrefix, + symbol = smallRowSymbol, + text = smallRowText + ) + + Spacer(modifier = Modifier.height(16.dp)) + + LargeRow( + prefix = largeRowPrefix, + text = largeRowText, + symbol = largeRowSymbol, + showSymbol = showSymbol + ) } } @Composable -private fun SmallRow(prefix: String?, text: String) { +fun LargeRow(prefix: String?, text: String, symbol: String, showSymbol: Boolean) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(24.dp) - .padding(bottom = 4.dp) + ) { + if (prefix != null) { + Display( + text = prefix, + color = Colors.White64, + modifier = Modifier.padding(end = 8.dp) + ) + } + if (showSymbol) { + Display( + text = symbol, + color = Colors.White64, + modifier = Modifier.padding(end = 8.dp) + ) + } + Display(text = text) + } +} + +@Composable +private fun SmallRow(prefix: String?, symbol: String?, text: String) { + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(4.dp), ) { if (prefix != null) { Caption13Up( text = prefix, - modifier = Modifier.alpha(0.6f) + color = Colors.White64, + ) + } + if (symbol != null) { + Caption13Up( + text = symbol, + color = Colors.White64, ) } Caption13Up( text = text, - modifier = Modifier.alpha(0.6f) + color = Colors.White64, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + AppThemeSurface { + BalanceHeader( + smallRowPrefix = "$", + smallRowText = "27.36", + largeRowPrefix = "₿", + largeRowText = "136 825", + largeRowSymbol = "sats", + showSymbol = false, + modifier = Modifier.fillMaxWidth(), + onClick = {} ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt index 8b4f2adf1..c202a0e51 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt @@ -10,6 +10,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -18,6 +22,7 @@ import to.bitkit.R import to.bitkit.models.PrimaryDisplay import to.bitkit.ui.LocalBalances import to.bitkit.ui.LocalCurrencies +import to.bitkit.ui.components.BalanceHeaderEditable import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.Keyboard import to.bitkit.ui.components.MoneySSB @@ -49,10 +54,17 @@ fun SendAmountScreen( onEvent(SendEvent.AmountReset) onBack() } + + var input: String by remember { mutableStateOf("") } + Column( modifier = Modifier.padding(horizontal = 16.dp) ) { - BalanceHeaderView(sats = uiState.amountInput.toLong(), modifier = Modifier.fillMaxWidth()) + BalanceHeaderEditable( + input = input, + onSatsChanged = { number -> onEvent(SendEvent.AmountChange(number)) }, + modifier = Modifier.fillMaxWidth() + ) Spacer(modifier = Modifier.height(24.dp)) @@ -105,7 +117,7 @@ fun SendAmountScreen( HorizontalDivider(modifier = Modifier.padding(vertical = 24.dp)) Keyboard( - onClick = { number -> onEvent(SendEvent.AmountChange(number)) }, + onClick = { number -> input = number }, isDecimal = currencyUiState.primaryDisplay == PrimaryDisplay.FIAT, modifier = Modifier.fillMaxWidth(), ) diff --git a/app/src/main/java/to/bitkit/viewmodels/CurrencyViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/CurrencyViewModel.kt index 0bb02ef06..817b31729 100644 --- a/app/src/main/java/to/bitkit/viewmodels/CurrencyViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/CurrencyViewModel.kt @@ -160,6 +160,7 @@ class CurrencyViewModel @Inject constructor( } // UI Helpers + /**Convert sats fo fiat*/ fun convert(sats: Long, currency: String? = null): ConvertedAmount? { val targetCurrency = currency ?: uiState.value.selectedCurrency val rate = currencyService.getCurrentRate(targetCurrency, uiState.value.rates)