Skip to content
Closed
26 changes: 26 additions & 0 deletions app/src/main/java/to/bitkit/models/Currency.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
270 changes: 196 additions & 74 deletions app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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(),
)
Expand Down
Loading
Loading