Skip to content

Commit 2912e30

Browse files
authored
Merge pull request #106 from synonymdev/feat/set-amount-invoice
Edit Invoice Screen
2 parents 89a4a92 + 4d8d2d1 commit 2912e30

File tree

8 files changed

+612
-81
lines changed

8 files changed

+612
-81
lines changed

app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.runtime.remember
1313
import androidx.compose.runtime.setValue
1414
import androidx.compose.ui.Alignment
1515
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.platform.LocalInspectionMode
1617
import androidx.compose.ui.semantics.contentDescription
1718
import androidx.compose.ui.semantics.semantics
1819
import androidx.compose.ui.tooling.preview.Preview
@@ -26,6 +27,8 @@ import to.bitkit.models.formatToModernDisplay
2627
import to.bitkit.ui.currencyViewModel
2728
import to.bitkit.ui.theme.AppThemeSurface
2829
import to.bitkit.ui.theme.Colors
30+
import to.bitkit.viewmodels.CurrencyViewModel
31+
import java.math.BigDecimal
2932

3033

3134
@Composable
@@ -35,6 +38,20 @@ fun NumberPadTextField(
3538
primaryDisplay: PrimaryDisplay,
3639
modifier: Modifier = Modifier,
3740
) {
41+
42+
val isPreview = LocalInspectionMode.current
43+
if (isPreview) {
44+
return MoneyAmount(
45+
modifier = modifier,
46+
value = input,
47+
unit = primaryDisplay,
48+
placeholder = "",
49+
showPlaceholder = true,
50+
satoshis = 0,
51+
currencySymbol = if (primaryDisplay == PrimaryDisplay.BITCOIN) BITCOIN_SYMBOL else "$"
52+
)
53+
}
54+
3855
val currency = currencyViewModel ?: return
3956

4057
val satoshis = if (primaryDisplay == PrimaryDisplay.FIAT) {
@@ -118,6 +135,48 @@ fun NumberPadTextField(
118135
)
119136
}
120137

138+
139+
@Composable
140+
fun AmountInputHandler(
141+
input: String,
142+
primaryDisplay: PrimaryDisplay,
143+
displayUnit: BitcoinDisplayUnit,
144+
onInputChanged: (String) -> Unit,
145+
onAmountCalculated: (String) -> Unit,
146+
currencyVM: CurrencyViewModel
147+
) {
148+
LaunchedEffect(primaryDisplay) {
149+
val newInput = when (primaryDisplay) {
150+
PrimaryDisplay.BITCOIN -> { //Convert fiat to sats
151+
val amountLong = currencyVM.convertFiatToSats(input.replace(",", "").toDoubleOrNull() ?: 0.0) ?: 0
152+
if (amountLong > 0.0) amountLong.toString() else ""
153+
}
154+
155+
PrimaryDisplay.FIAT -> { //Convert sats to fiat
156+
val convertedAmount = currencyVM.convert(input.toLongOrDefault(0L))
157+
if ((convertedAmount?.value
158+
?: BigDecimal(0)) > BigDecimal(0)
159+
) convertedAmount?.formatted.toString() else ""
160+
}
161+
}
162+
onInputChanged(newInput)
163+
}
164+
165+
LaunchedEffect(input) {
166+
val sats = when (primaryDisplay) {
167+
PrimaryDisplay.BITCOIN -> {
168+
if (displayUnit == BitcoinDisplayUnit.MODERN) input else (input.toLongOrDefault(0L) * 100_000_000).toString()
169+
}
170+
171+
PrimaryDisplay.FIAT -> {
172+
val convertedAmount = currencyVM.convertFiatToSats(input.replace(",", "").toDoubleOrNull() ?: 0.0) ?: 0L
173+
convertedAmount.toString()
174+
}
175+
}
176+
onAmountCalculated(sats)
177+
}
178+
}
179+
121180
@Composable
122181
fun MoneyAmount(
123182
modifier: Modifier = Modifier,
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
package to.bitkit.ui.screens.wallets.receive
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.animation.fadeIn
6+
import androidx.compose.animation.fadeOut
7+
import androidx.compose.animation.slideInVertically
8+
import androidx.compose.animation.slideOutVertically
9+
import androidx.compose.foundation.layout.Arrangement
10+
import androidx.compose.foundation.layout.Column
11+
import androidx.compose.foundation.layout.Row
12+
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.height
16+
import androidx.compose.foundation.layout.navigationBarsPadding
17+
import androidx.compose.foundation.layout.padding
18+
import androidx.compose.material3.HorizontalDivider
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.TextField
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableStateOf
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.runtime.setValue
26+
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.platform.testTag
29+
import androidx.compose.ui.res.stringResource
30+
import androidx.compose.ui.tooling.preview.Preview
31+
import androidx.compose.ui.unit.dp
32+
import to.bitkit.R
33+
import to.bitkit.models.BitcoinDisplayUnit
34+
import to.bitkit.models.PrimaryDisplay
35+
import to.bitkit.ui.LocalCurrencies
36+
import to.bitkit.ui.components.AmountInputHandler
37+
import to.bitkit.ui.components.BodySSB
38+
import to.bitkit.ui.components.Caption13Up
39+
import to.bitkit.ui.components.Keyboard
40+
import to.bitkit.ui.components.NumberPadTextField
41+
import to.bitkit.ui.components.PrimaryButton
42+
import to.bitkit.ui.components.UnitButton
43+
import to.bitkit.ui.currencyViewModel
44+
import to.bitkit.ui.scaffold.SheetTopBar
45+
import to.bitkit.ui.shared.util.clickableAlpha
46+
import to.bitkit.ui.shared.util.gradientBackground
47+
import to.bitkit.ui.theme.AppTextFieldDefaults
48+
import to.bitkit.ui.theme.AppThemeSurface
49+
import to.bitkit.ui.theme.Colors
50+
import to.bitkit.viewmodels.CurrencyUiState
51+
52+
@Composable
53+
fun EditInvoiceScreen(
54+
currencyUiState: CurrencyUiState = LocalCurrencies.current,
55+
updateInvoice: (ULong?, String) -> Unit,
56+
onBack: () -> Unit,
57+
) {
58+
val currencyVM = currencyViewModel ?: return
59+
var input: String by remember { mutableStateOf("") }
60+
var noteText by remember { mutableStateOf("") }
61+
var satsString by remember { mutableStateOf("") }
62+
var keyboardVisible by remember { mutableStateOf(false) }
63+
64+
AmountInputHandler(
65+
input = input,
66+
primaryDisplay = currencyUiState.primaryDisplay,
67+
displayUnit = currencyUiState.displayUnit,
68+
onInputChanged = { newInput -> input = newInput },
69+
onAmountCalculated = { sats -> satsString = sats },
70+
currencyVM = currencyVM
71+
)
72+
73+
EditInvoiceContent(
74+
input = input,
75+
noteText = noteText,
76+
primaryDisplay = currencyUiState.primaryDisplay,
77+
displayUnit = currencyUiState.displayUnit,
78+
onBack = onBack,
79+
onTextChanged = { newNote -> noteText = newNote },
80+
keyboardVisible = keyboardVisible,
81+
onClickBalance = { keyboardVisible = true },
82+
onInputChanged = { newText -> input = newText },
83+
onContinueKeyboard = { keyboardVisible = false },
84+
onContinueGeneral = { updateInvoice(satsString.toULongOrNull(), noteText) }
85+
)
86+
}
87+
88+
@Composable
89+
fun EditInvoiceContent(
90+
input: String,
91+
noteText: String,
92+
keyboardVisible: Boolean,
93+
primaryDisplay: PrimaryDisplay,
94+
displayUnit: BitcoinDisplayUnit,
95+
onBack: () -> Unit,
96+
onContinueKeyboard: () -> Unit,
97+
onClickBalance: () -> Unit,
98+
onContinueGeneral: () -> Unit,
99+
onTextChanged: (String) -> Unit,
100+
onInputChanged: (String) -> Unit,
101+
) {
102+
Column(
103+
modifier = Modifier
104+
.fillMaxSize()
105+
.gradientBackground()
106+
.navigationBarsPadding()
107+
.testTag("edit_invoice_screen")
108+
) {
109+
SheetTopBar(stringResource(R.string.wallet__receive_specify)) {
110+
onBack()
111+
}
112+
113+
Column(
114+
modifier = Modifier
115+
.padding(horizontal = 16.dp)
116+
.testTag("edit_invoice_content")
117+
) {
118+
Spacer(Modifier.height(32.dp))
119+
120+
NumberPadTextField(
121+
input = input,
122+
displayUnit = displayUnit,
123+
primaryDisplay = primaryDisplay,
124+
modifier = Modifier
125+
.fillMaxWidth()
126+
.clickableAlpha(onClick = onClickBalance)
127+
.testTag("amount_input_field")
128+
)
129+
130+
// Animated visibility for keyboard section
131+
AnimatedVisibility(
132+
visible = keyboardVisible,
133+
enter = slideInVertically(
134+
initialOffsetY = { fullHeight -> fullHeight },
135+
animationSpec = tween(durationMillis = 300)
136+
) + fadeIn(),
137+
exit = slideOutVertically(
138+
targetOffsetY = { fullHeight -> fullHeight },
139+
animationSpec = tween(durationMillis = 300)
140+
) + fadeOut()
141+
) {
142+
Column(
143+
modifier = Modifier.testTag("keyboard_section")
144+
) {
145+
Spacer(modifier = Modifier.weight(1f))
146+
147+
Row(
148+
verticalAlignment = Alignment.CenterVertically,
149+
horizontalArrangement = Arrangement.End,
150+
modifier = Modifier.fillMaxWidth()
151+
) {
152+
UnitButton(modifier = Modifier.height(28.dp))
153+
}
154+
155+
HorizontalDivider(modifier = Modifier.padding(vertical = 24.dp))
156+
157+
Keyboard(
158+
onClick = { number ->
159+
onInputChanged(if (input == "0") number else input + number)
160+
},
161+
onClickBackspace = {
162+
onInputChanged(if (input.length > 1) input.dropLast(1) else "0")
163+
},
164+
isDecimal = primaryDisplay == PrimaryDisplay.FIAT,
165+
modifier = Modifier
166+
.fillMaxWidth()
167+
.testTag("amount_keyboard"),
168+
)
169+
170+
Spacer(modifier = Modifier.height(41.dp))
171+
172+
PrimaryButton(
173+
text = stringResource(R.string.continue_button),
174+
onClick = onContinueKeyboard,
175+
modifier = Modifier.testTag("keyboard_continue_button")
176+
)
177+
178+
Spacer(modifier = Modifier.height(16.dp))
179+
}
180+
}
181+
182+
// Animated visibility for note section
183+
AnimatedVisibility(
184+
visible = !keyboardVisible,
185+
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
186+
exit = fadeOut(animationSpec = tween(durationMillis = 300))
187+
) {
188+
Column(
189+
modifier = Modifier.testTag("note_section")
190+
) {
191+
Spacer(modifier = Modifier.height(44.dp))
192+
193+
Caption13Up(text = stringResource(R.string.wallet__note), color = Colors.White64)
194+
195+
Spacer(modifier = Modifier.height(16.dp))
196+
197+
TextField(
198+
placeholder = {
199+
BodySSB(
200+
text = stringResource(R.string.wallet__receive_note_placeholder),
201+
color = Colors.White64
202+
)
203+
},
204+
value = noteText,
205+
onValueChange = onTextChanged,
206+
minLines = 4,
207+
colors = AppTextFieldDefaults.semiTransparent,
208+
shape = MaterialTheme.shapes.medium,
209+
modifier = Modifier
210+
.fillMaxWidth()
211+
.testTag("note_input_field")
212+
213+
)
214+
215+
Spacer(modifier = Modifier.height(16.dp))
216+
217+
Spacer(modifier = Modifier.weight(1f))
218+
219+
PrimaryButton(
220+
text = stringResource(R.string.continue_button),
221+
onClick = onContinueGeneral,
222+
modifier = Modifier.testTag("general_continue_button")
223+
)
224+
225+
Spacer(modifier = Modifier.height(16.dp))
226+
}
227+
}
228+
}
229+
}
230+
}
231+
232+
@Preview(showBackground = true)
233+
@Composable
234+
private fun Preview() {
235+
AppThemeSurface {
236+
EditInvoiceContent(
237+
input = "123",
238+
noteText = "",
239+
primaryDisplay = PrimaryDisplay.BITCOIN,
240+
displayUnit = BitcoinDisplayUnit.MODERN,
241+
onBack = {},
242+
onTextChanged = {},
243+
keyboardVisible = false,
244+
onClickBalance = {},
245+
onInputChanged = {},
246+
onContinueGeneral = {},
247+
onContinueKeyboard = {}
248+
)
249+
}
250+
}
251+
252+
@Preview(showBackground = true)
253+
@Composable
254+
private fun Preview2() {
255+
AppThemeSurface {
256+
EditInvoiceContent(
257+
input = "123",
258+
noteText = "Note text",
259+
primaryDisplay = PrimaryDisplay.BITCOIN,
260+
displayUnit = BitcoinDisplayUnit.MODERN,
261+
onBack = {},
262+
onTextChanged = {},
263+
keyboardVisible = false,
264+
onClickBalance = {},
265+
onInputChanged = {},
266+
onContinueGeneral = {},
267+
onContinueKeyboard = {}
268+
)
269+
}
270+
}
271+
272+
@Preview(showBackground = true)
273+
@Composable
274+
private fun Preview3() {
275+
AppThemeSurface {
276+
EditInvoiceContent(
277+
input = "123",
278+
noteText = "Note text",
279+
primaryDisplay = PrimaryDisplay.BITCOIN,
280+
displayUnit = BitcoinDisplayUnit.MODERN,
281+
onBack = {},
282+
onTextChanged = {},
283+
keyboardVisible = true,
284+
onClickBalance = {},
285+
onInputChanged = {},
286+
onContinueGeneral = {},
287+
onContinueKeyboard = {}
288+
)
289+
}
290+
}

0 commit comments

Comments
 (0)