Skip to content

Commit 75ba763

Browse files
committed
refactor: custom numberpad
1 parent 3fe822c commit 75ba763

32 files changed

+2211
-1210
lines changed

app/src/androidTest/java/to/bitkit/ui/components/KeyboardTest.kt

Lines changed: 0 additions & 90 deletions
This file was deleted.

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

Lines changed: 20 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import androidx.compose.ui.test.onNodeWithTag
66
import androidx.compose.ui.test.performClick
77
import org.junit.Rule
88
import org.junit.Test
9-
import to.bitkit.models.BitcoinDisplayUnit
109
import to.bitkit.models.NodeLifecycleState
1110
import to.bitkit.models.PrimaryDisplay
12-
import to.bitkit.viewmodels.CurrencyUiState
11+
import to.bitkit.repositories.CurrencyState
1312
import to.bitkit.viewmodels.MainUiState
14-
import to.bitkit.viewmodels.SendEvent
1513
import to.bitkit.viewmodels.SendMethod
1614
import to.bitkit.viewmodels.SendUiState
15+
import to.bitkit.viewmodels.previewAmountInputViewModel
1716

1817
class SendAmountContentTest {
1918

@@ -22,8 +21,7 @@ class SendAmountContentTest {
2221

2322
private val testUiState = SendUiState(
2423
payMethod = SendMethod.LIGHTNING,
25-
amountInput = "100",
26-
isAmountInputValid = true,
24+
amount = 100u,
2725
isUnified = true
2826
)
2927

@@ -35,21 +33,15 @@ class SendAmountContentTest {
3533
fun whenScreenLoaded_shouldShowAllComponents() {
3634
composeTestRule.setContent {
3735
SendAmountContent(
38-
input = "100",
39-
uiState = testUiState,
4036
walletUiState = testWalletState,
41-
currencyUiState = CurrencyUiState(primaryDisplay = PrimaryDisplay.BITCOIN),
42-
displayUnit = BitcoinDisplayUnit.MODERN,
43-
primaryDisplay = PrimaryDisplay.BITCOIN,
44-
onInputChanged = {},
45-
onEvent = {},
46-
onBack = {}
37+
uiState = testUiState,
38+
amountInputViewModel = previewAmountInputViewModel(),
4739
)
4840
}
4941

5042
composeTestRule.onNodeWithTag("send_amount_screen").assertExists()
51-
// composeTestRule.onNodeWithTag("amount_input_field").assertExists() doesn't displayed because of viewmodel injection
52-
composeTestRule.onNodeWithTag("available_balance").assertExists()
43+
composeTestRule.onNodeWithTag("SendNumberField").assertExists()
44+
composeTestRule.onNodeWithTag("available_balance", useUnmergedTree = true).assertExists()
5345
composeTestRule.onNodeWithTag("AssetButton-switch").assertExists()
5446
composeTestRule.onNodeWithTag("ContinueAmount").assertExists()
5547
composeTestRule.onNodeWithTag("SendAmountNumberPad").assertExists()
@@ -59,42 +51,27 @@ class SendAmountContentTest {
5951
fun whenNodeNotRunning_shouldShowSyncView() {
6052
composeTestRule.setContent {
6153
SendAmountContent(
62-
input = "100",
63-
uiState = testUiState,
6454
walletUiState = MainUiState(
6555
nodeLifecycleState = NodeLifecycleState.Initializing
6656
),
67-
displayUnit = BitcoinDisplayUnit.MODERN,
68-
primaryDisplay = PrimaryDisplay.BITCOIN,
69-
currencyUiState = CurrencyUiState(),
70-
onInputChanged = {},
71-
onEvent = {},
72-
onBack = {}
57+
uiState = testUiState,
58+
amountInputViewModel = previewAmountInputViewModel(),
7359
)
7460
}
7561

7662
composeTestRule.onNodeWithTag("sync_node_view").assertExists()
77-
composeTestRule.onNodeWithTag("amount_input_field").assertDoesNotExist()
63+
composeTestRule.onNodeWithTag("SendNumberField").assertDoesNotExist()
7864
}
7965

8066
@Test
8167
fun whenPaymentMethodButtonClicked_shouldTriggerEvent() {
8268
var eventTriggered = false
8369
composeTestRule.setContent {
8470
SendAmountContent(
85-
input = "100",
86-
uiState = testUiState,
8771
walletUiState = testWalletState,
88-
currencyUiState = CurrencyUiState(),
89-
onInputChanged = {},
90-
onEvent = { event ->
91-
if (event is SendEvent.PaymentMethodSwitch) {
92-
eventTriggered = true
93-
}
94-
},
95-
displayUnit = BitcoinDisplayUnit.MODERN,
96-
primaryDisplay = PrimaryDisplay.BITCOIN,
97-
onBack = {}
72+
uiState = testUiState,
73+
amountInputViewModel = previewAmountInputViewModel(),
74+
onClickPayMethod = { eventTriggered = true }
9875
)
9976
}
10077

@@ -109,23 +86,14 @@ class SendAmountContentTest {
10986
var eventTriggered = false
11087
composeTestRule.setContent {
11188
SendAmountContent(
112-
input = "100",
113-
uiState = testUiState.copy(isAmountInputValid = true),
11489
walletUiState = testWalletState,
115-
currencyUiState = CurrencyUiState(),
116-
onInputChanged = {},
117-
onEvent = { event ->
118-
if (event is SendEvent.AmountContinue) {
119-
eventTriggered = true
120-
}
121-
},
122-
displayUnit = BitcoinDisplayUnit.MODERN,
123-
primaryDisplay = PrimaryDisplay.BITCOIN,
124-
onBack = {}
90+
uiState = testUiState,
91+
amountInputViewModel = previewAmountInputViewModel(),
92+
onContinue = { eventTriggered = true }
12593
)
12694
}
12795

128-
composeTestRule.onNodeWithTag("continue_button")
96+
composeTestRule.onNodeWithTag("ContinueAmount")
12997
.performClick()
13098

13199
assert(eventTriggered)
@@ -135,18 +103,12 @@ class SendAmountContentTest {
135103
fun whenAmountInvalid_continueButtonShouldBeDisabled() {
136104
composeTestRule.setContent {
137105
SendAmountContent(
138-
input = "100",
139-
uiState = testUiState.copy(isAmountInputValid = false),
140106
walletUiState = testWalletState,
141-
currencyUiState = CurrencyUiState(),
142-
onInputChanged = {},
143-
onEvent = {},
144-
displayUnit = BitcoinDisplayUnit.MODERN,
145-
primaryDisplay = PrimaryDisplay.BITCOIN,
146-
onBack = {}
107+
uiState = testUiState.copy(amount = 0u),
108+
amountInputViewModel = previewAmountInputViewModel(),
147109
)
148110
}
149111

150-
composeTestRule.onNodeWithTag("continue_button").assertIsNotEnabled()
112+
composeTestRule.onNodeWithTag("ContinueAmount").assertIsNotEnabled()
151113
}
152114
}
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.AmountInputHandler
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+
@Suppress("unused")
17+
@Binds
18+
abstract fun bindAmountInputHandler(currencyRepo: CurrencyRepo): AmountInputHandler
19+
20+
companion object {
21+
@Suppress("FunctionOnlyReturningConstant")
22+
@Provides
23+
@Named("enablePolling")
24+
fun provideEnablePolling(): Boolean = true
25+
}
1626
}

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ import java.text.DecimalFormat
88
import java.text.DecimalFormatSymbols
99
import java.util.Locale
1010

11+
const val STUB_RATE = 115_150.0
1112
const val BITCOIN_SYMBOL = ""
13+
const val USD_SYMBOL = "$"
1214
const val SATS_IN_BTC = 100_000_000
1315
const val BTC_SCALE = 8
14-
const val GROUPING_SEPARATOR = ' '
16+
const val SATS_GROUPING_SEPARATOR = ' '
17+
const val FIAT_GROUPING_SEPARATOR = ','
18+
const val DECIMAL_SEPARATOR = '.'
19+
const val CLASSIC_DECIMALS = 8
20+
const val FIAT_DECIMALS = 2
1521

1622
@Serializable
1723
data class FxRateResponse(
@@ -87,10 +93,10 @@ data class ConvertedAmount(
8793

8894
fun Long.formatToModernDisplay(locale: Locale = Locale.getDefault()): String {
8995
val sats = this
90-
val formatSymbols = DecimalFormatSymbols(locale).apply {
91-
groupingSeparator = GROUPING_SEPARATOR
96+
val symbols = DecimalFormatSymbols(locale).apply {
97+
groupingSeparator = SATS_GROUPING_SEPARATOR
9298
}
93-
val formatter = DecimalFormat("#,###", formatSymbols).apply {
99+
val formatter = DecimalFormat("#,###", symbols).apply {
94100
isGroupingUsed = true
95101
}
96102
return formatter.format(sats)
@@ -100,10 +106,28 @@ fun ULong.formatToModernDisplay(locale: Locale = Locale.getDefault()): String =
100106

101107
fun Long.formatToClassicDisplay(locale: Locale = Locale.getDefault()): String {
102108
val sats = this
103-
val formatSymbols = DecimalFormatSymbols(locale)
104-
val formatter = DecimalFormat("###.########", formatSymbols)
109+
val symbols = DecimalFormatSymbols(locale).apply {
110+
decimalSeparator = DECIMAL_SEPARATOR
111+
}
112+
val formatter = DecimalFormat("###.########", symbols)
105113
return formatter.format(sats.asBtc())
106114
}
107115

116+
fun BigDecimal.formatCurrency(decimalPlaces: Int = FIAT_DECIMALS, locale: Locale = Locale.getDefault()): String? {
117+
val symbols = DecimalFormatSymbols(locale).apply {
118+
decimalSeparator = DECIMAL_SEPARATOR
119+
groupingSeparator = FIAT_GROUPING_SEPARATOR
120+
}
121+
122+
val decimalPlacesString = "0".repeat(decimalPlaces)
123+
val formatter = DecimalFormat("#,##0.$decimalPlacesString", symbols).apply {
124+
minimumFractionDigits = decimalPlaces
125+
maximumFractionDigits = decimalPlaces
126+
isGroupingUsed = true
127+
}
128+
129+
return runCatching { formatter.format(this) }.getOrNull()
130+
}
131+
108132
/** Represent this sat value in Bitcoin BigDecimal. */
109133
fun Long.asBtc(): BigDecimal = BigDecimal(this).divide(BigDecimal(SATS_IN_BTC), BTC_SCALE, RoundingMode.HALF_UP)

0 commit comments

Comments
 (0)