Skip to content

Commit 8209f6d

Browse files
committed
refactor: custom numberpad
1 parent dda1c05 commit 8209f6d

File tree

15 files changed

+1473
-285
lines changed

15 files changed

+1473
-285
lines changed

app/src/androidTest/java/to/bitkit/ui/screens/wallets/send/SendAmountContentTest.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class SendAmountContentTest {
2222

2323
private val testUiState = SendUiState(
2424
payMethod = SendMethod.LIGHTNING,
25-
amountInput = "100",
26-
isAmountInputValid = true,
25+
amount = 100u,
2726
isUnified = true
2827
)
2928

@@ -110,7 +109,7 @@ class SendAmountContentTest {
110109
composeTestRule.setContent {
111110
SendAmountContent(
112111
input = "100",
113-
uiState = testUiState.copy(isAmountInputValid = true),
112+
uiState = testUiState,
114113
walletUiState = testWalletState,
115114
currencyUiState = CurrencyUiState(),
116115
onInputChanged = {},
@@ -136,7 +135,7 @@ class SendAmountContentTest {
136135
composeTestRule.setContent {
137136
SendAmountContent(
138137
input = "100",
139-
uiState = testUiState.copy(isAmountInputValid = false),
138+
uiState = testUiState.copy(amount = 0u),
140139
walletUiState = testWalletState,
141140
currencyUiState = CurrencyUiState(),
142141
onInputChanged = {},
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
package to.bitkit.di
22

3+
import dagger.Binds
34
import dagger.Module
45
import dagger.Provides
56
import dagger.hilt.InstallIn
67
import dagger.hilt.components.SingletonComponent
8+
import to.bitkit.repositories.CurrencyConverter
9+
import to.bitkit.repositories.CurrencyRepo
710
import javax.inject.Named
811

912
@Module
1013
@InstallIn(SingletonComponent::class)
11-
object RepoModule {
14+
abstract class RepoModule {
1215

13-
@Provides
14-
@Named("enablePolling")
15-
fun provideEnablePolling(): Boolean = true
16+
@Binds
17+
abstract fun bindCurrencyConverter(
18+
currencyRepo: CurrencyRepo
19+
): CurrencyConverter
20+
21+
companion object {
22+
@Provides
23+
@Named("enablePolling")
24+
fun provideEnablePolling(): Boolean = true
25+
}
1626
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package to.bitkit.models
2+
3+
enum class NumberPadType {
4+
SIMPLE, // No special bottom-left button
5+
INTEGER, // "000" button for modern Bitcoin
6+
DECIMAL // "." button for classic Bitcoin/fiat
7+
}
8+
9+
object AmountConstants {
10+
const val MAX_RECEIVE_AMOUNT = 999_999_999L // sats
11+
const val MAX_MODERN_BITCOIN_LENGTH = 10
12+
const val MAX_DECIMAL_INPUT_LENGTH = 20
13+
const val CLASSIC_BITCOIN_DECIMALS = 8
14+
const val FIAT_DECIMALS = 2
15+
const val ERROR_CLEAR_DELAY = 500L // ms
16+
17+
const val GROUPING_SEPARATOR_BITCOIN = " "
18+
const val GROUPING_SEPARATOR_FIAT = ","
19+
const val DECIMAL_SEPARATOR = "."
20+
}

app/src/main/java/to/bitkit/models/Currency.kt

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const val SATS_IN_BTC = 100_000_000
1313
const val BTC_SCALE = 8
1414
const val BTC_PLACEHOLDER = "0.00000000"
1515
const val SATS_PLACEHOLDER = "0"
16+
const val GROUPING_SEPARATOR = ' '
1617

1718
@Serializable
1819
data class FxRateResponse(
@@ -69,28 +70,17 @@ data class ConvertedAmount(
6970
val currency: String,
7071
val flag: String,
7172
val sats: Long,
73+
val locale: Locale = Locale.getDefault(),
7274
) {
73-
val btcValue: BigDecimal = sats.asBtc()
74-
7575
data class BitcoinDisplayComponents(
7676
val symbol: String,
7777
val value: String,
7878
)
7979

8080
fun bitcoinDisplay(unit: BitcoinDisplayUnit): BitcoinDisplayComponents {
81-
val spaceSeparator = ' '
8281
val formattedValue = when (unit) {
83-
BitcoinDisplayUnit.MODERN -> {
84-
sats.formatToModernDisplay()
85-
}
86-
87-
BitcoinDisplayUnit.CLASSIC -> {
88-
val formatSymbols = DecimalFormatSymbols(Locale.getDefault()).apply {
89-
groupingSeparator = spaceSeparator
90-
}
91-
val formatter = DecimalFormat("#,###.########", formatSymbols)
92-
formatter.format(btcValue)
93-
}
82+
BitcoinDisplayUnit.MODERN -> sats.formatToModernDisplay(locale)
83+
BitcoinDisplayUnit.CLASSIC -> sats.formatToClassicDisplay(locale)
9484
}
9585
return BitcoinDisplayComponents(
9686
symbol = BITCOIN_SYMBOL,
@@ -99,18 +89,37 @@ data class ConvertedAmount(
9989
}
10090
}
10191

102-
fun Long.formatToModernDisplay(): String {
92+
fun Long.formatToModernDisplay(locale: Locale = Locale.getDefault()): String {
10393
val sats = this
104-
val formatSymbols = DecimalFormatSymbols(Locale.getDefault()).apply {
105-
groupingSeparator = ' '
94+
val formatSymbols = DecimalFormatSymbols(locale).apply {
95+
groupingSeparator = GROUPING_SEPARATOR
10696
}
10797
val formatter = DecimalFormat("#,###", formatSymbols).apply {
10898
isGroupingUsed = true
10999
}
110100
return formatter.format(sats)
111101
}
112102

113-
fun ULong.formatToModernDisplay(): String = this.toLong().formatToModernDisplay()
103+
fun ULong.formatToModernDisplay(locale: Locale = Locale.getDefault()): String = toLong().formatToModernDisplay(locale)
104+
105+
fun Long.formatToClassicDisplay(locale: Locale = Locale.getDefault()): String {
106+
val sats = this
107+
val formatSymbols = DecimalFormatSymbols(locale)
108+
val formatter = DecimalFormat("###.########", formatSymbols)
109+
return formatter.format(sats.asBtc())
110+
}
114111

115112
/** Represent this sat value in Bitcoin BigDecimal. */
116113
fun Long.asBtc(): BigDecimal = BigDecimal(this).divide(BigDecimal(SATS_IN_BTC), BTC_SCALE, RoundingMode.HALF_UP)
114+
115+
fun String.btcToSats(): Long {
116+
if (isEmpty()) return 0L
117+
return runCatching {
118+
val btcBigDecimal = BigDecimal(this)
119+
val satsBigDecimal = btcBigDecimal.multiply(BigDecimal(SATS_IN_BTC))
120+
satsBigDecimal.toLong()
121+
}.fold(
122+
onSuccess = { it },
123+
onFailure = { 0L },
124+
)
125+
}

app/src/main/java/to/bitkit/repositories/CurrencyRepo.kt

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import to.bitkit.ui.utils.formatCurrency
3737
import to.bitkit.utils.Logger
3838
import java.math.BigDecimal
3939
import java.math.RoundingMode
40+
import java.util.Locale
4041
import javax.inject.Inject
4142
import javax.inject.Named
4243
import javax.inject.Singleton
@@ -49,7 +50,7 @@ class CurrencyRepo @Inject constructor(
4950
private val cacheStore: CacheStore,
5051
@Named("enablePolling") private val enablePolling: Boolean,
5152
private val clock: Clock,
52-
) {
53+
) : CurrencyConverter {
5354
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
5455
private val _currencyState = MutableStateFlow(CurrencyState())
5556
val currencyState: StateFlow<CurrencyState> = _currencyState.asStateFlow()
@@ -177,10 +178,6 @@ class CurrencyRepo @Inject constructor(
177178
refresh()
178179
}
179180

180-
fun getCurrencySymbol(): String {
181-
return _currencyState.value.currencySymbol
182-
}
183-
184181
fun getCurrentRate(currency: String): FxRate? {
185182
return _currencyState.value.rates.firstOrNull { it.quote == currency }
186183
}
@@ -235,19 +232,29 @@ class CurrencyRepo @Inject constructor(
235232

236233
fun convertFiatToSats(
237234
fiatAmount: Double,
238-
currency: String?
235+
currency: String?,
239236
): Result<ULong> {
240237
return convertFiatToSats(
241238
fiatValue = BigDecimal.valueOf(fiatAmount),
242-
currency = currency
239+
currency = currency,
243240
)
244241
}
245242

246243
companion object {
247244
private const val TAG = "CurrencyRepo"
248245
}
246+
247+
// MARK: - CurrencyConverter
248+
override fun convertFiatToSats(fiat: Double): Long =
249+
convertFiatToSats(BigDecimal.valueOf(fiat)).getOrNull()?.toLong() ?: 0L
250+
251+
override fun convertSatsToFiatString(sats: Long): String =
252+
convertSatsToFiat(sats, null).getOrNull()?.formatted ?: ""
253+
249254
}
250255

256+
// region contract
257+
251258
data class CurrencyState(
252259
val rates: List<FxRate> = emptyList(),
253260
val error: Throwable? = null,
@@ -256,5 +263,18 @@ data class CurrencyState(
256263
val currencySymbol: String = "$",
257264
val displayUnit: BitcoinDisplayUnit = BitcoinDisplayUnit.MODERN,
258265
val primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN,
259-
val lastSuccessfulRefresh: Long? = null
266+
val lastSuccessfulRefresh: Long? = null,
260267
)
268+
269+
interface CurrencyConverter {
270+
fun convertSatsToFiatString(sats: Long): String
271+
fun convertFiatToSats(fiat: Double): Long
272+
}
273+
274+
object NoopCurrencyConverter : CurrencyConverter {
275+
const val RATE: Double = 100000.0
276+
override fun convertSatsToFiatString(sats: Long): String = "%.2f".format(Locale.US, sats / RATE)
277+
override fun convertFiatToSats(fiat: Double): Long = (fiat * RATE).toLong()
278+
}
279+
280+
// endregion

0 commit comments

Comments
 (0)