Skip to content

Commit 972363b

Browse files
committed
Add gift codes support
1 parent 912cdd2 commit 972363b

File tree

10 files changed

+570
-1
lines changed

10 files changed

+570
-1
lines changed

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ import to.bitkit.ui.sheets.BackgroundPaymentsIntroSheet
147147
import to.bitkit.ui.sheets.BackupRoute
148148
import to.bitkit.ui.sheets.BackupSheet
149149
import to.bitkit.ui.sheets.ForceTransferSheet
150+
import to.bitkit.ui.sheets.GiftSheet
150151
import to.bitkit.ui.sheets.HighBalanceWarningSheet
151152
import to.bitkit.ui.sheets.LnurlAuthSheet
152153
import to.bitkit.ui.sheets.PinSheet
@@ -363,6 +364,7 @@ fun ContentView(
363364
is Sheet.Backup -> BackupSheet(sheet, onDismiss = { appViewModel.hideSheet() })
364365
is Sheet.LnurlAuth -> LnurlAuthSheet(sheet, appViewModel)
365366
Sheet.ForceTransfer -> ForceTransferSheet(appViewModel, transferViewModel)
367+
is Sheet.Gift -> GiftSheet(sheet, appViewModel)
366368
is Sheet.TimedSheet -> {
367369
when (sheet.type) {
368370
TimedSheetType.APP_UPDATE -> {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ sealed interface Sheet {
3939
data object ActivityTagSelector : Sheet
4040
data class LnurlAuth(val domain: String, val lnurl: String, val k1: String) : Sheet
4141
data object ForceTransfer : Sheet
42+
data class Gift(val code: String, val amount: ULong) : Sheet
4243

4344
data class TimedSheet(val type: TimedSheetType) : Sheet
4445
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package to.bitkit.ui.sheets
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.foundation.Image
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.aspectRatio
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.navigationBarsPadding
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.platform.testTag
15+
import androidx.compose.ui.res.painterResource
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.unit.dp
18+
import to.bitkit.R
19+
import to.bitkit.ui.components.BodyM
20+
import to.bitkit.ui.components.PrimaryButton
21+
import to.bitkit.ui.components.SheetSize
22+
import to.bitkit.ui.components.VerticalSpacer
23+
import to.bitkit.ui.scaffold.SheetTopBar
24+
import to.bitkit.ui.shared.modifiers.sheetHeight
25+
import to.bitkit.ui.shared.util.gradientBackground
26+
import to.bitkit.ui.theme.Colors
27+
28+
@Composable
29+
fun GiftErrorSheet(
30+
@StringRes titleRes: Int,
31+
@StringRes textRes: Int,
32+
testTag: String,
33+
onDismiss: () -> Unit,
34+
) {
35+
Content(
36+
titleRes = titleRes,
37+
textRes = textRes,
38+
testTag = testTag,
39+
onDismiss = onDismiss,
40+
)
41+
}
42+
43+
@Composable
44+
private fun Content(
45+
@StringRes titleRes: Int,
46+
@StringRes textRes: Int,
47+
testTag: String,
48+
modifier: Modifier = Modifier,
49+
onDismiss: () -> Unit = {},
50+
) {
51+
Column(
52+
modifier = modifier
53+
.sheetHeight(SheetSize.LARGE)
54+
.gradientBackground()
55+
.navigationBarsPadding()
56+
.padding(horizontal = 16.dp)
57+
) {
58+
SheetTopBar(titleText = stringResource(titleRes))
59+
VerticalSpacer(16.dp)
60+
61+
BodyM(
62+
text = stringResource(textRes),
63+
color = Colors.White64,
64+
)
65+
66+
Spacer(modifier = Modifier.weight(1f))
67+
68+
Image(
69+
painter = painterResource(R.drawable.exclamation_mark),
70+
contentDescription = null,
71+
modifier = Modifier
72+
.fillMaxWidth(IMAGE_WIDTH_FRACTION)
73+
.aspectRatio(1.0f)
74+
.align(Alignment.CenterHorizontally)
75+
)
76+
77+
Spacer(modifier = Modifier.weight(1f))
78+
79+
PrimaryButton(
80+
text = stringResource(R.string.common__ok),
81+
onClick = onDismiss,
82+
modifier = Modifier
83+
.fillMaxWidth()
84+
.testTag(testTag),
85+
)
86+
VerticalSpacer(16.dp)
87+
}
88+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package to.bitkit.ui.sheets
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.aspectRatio
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.navigationBarsPadding
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.material3.CircularProgressIndicator
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.remember
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.platform.testTag
17+
import androidx.compose.ui.res.painterResource
18+
import androidx.compose.ui.res.stringResource
19+
import androidx.compose.ui.unit.dp
20+
import to.bitkit.R
21+
import to.bitkit.models.BITCOIN_SYMBOL
22+
import to.bitkit.models.PrimaryDisplay
23+
import to.bitkit.models.formatToModernDisplay
24+
import to.bitkit.ui.LocalCurrencies
25+
import to.bitkit.ui.components.BodyM
26+
import to.bitkit.ui.components.Display
27+
import to.bitkit.ui.components.MoneySSB
28+
import to.bitkit.ui.components.SheetSize
29+
import to.bitkit.ui.components.VerticalSpacer
30+
import to.bitkit.ui.currencyViewModel
31+
import to.bitkit.ui.scaffold.SheetTopBar
32+
import to.bitkit.ui.shared.modifiers.sheetHeight
33+
import to.bitkit.ui.shared.util.gradientBackground
34+
import to.bitkit.ui.theme.Colors
35+
36+
@Composable
37+
fun GiftLoading(
38+
viewModel: GiftViewModel,
39+
) {
40+
Content(
41+
amount = viewModel.amount,
42+
)
43+
}
44+
45+
@Composable
46+
private fun Content(
47+
amount: ULong,
48+
modifier: Modifier = Modifier,
49+
) {
50+
Column(
51+
modifier = modifier
52+
.sheetHeight(SheetSize.LARGE)
53+
.gradientBackground()
54+
.navigationBarsPadding()
55+
.padding(horizontal = 16.dp)
56+
) {
57+
SheetTopBar(titleText = stringResource(R.string.other__gift__claiming__title))
58+
VerticalSpacer(16.dp)
59+
60+
val currencies = LocalCurrencies.current
61+
val currency = currencyViewModel
62+
val primaryDisplay = currencies.primaryDisplay
63+
64+
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
65+
MoneySSB(
66+
sats = amount.toLong(),
67+
unit = PrimaryDisplay.FIAT,
68+
color = Colors.White64,
69+
showSymbol = true,
70+
modifier = Modifier.align(Alignment.Start),
71+
)
72+
VerticalSpacer(16.dp)
73+
val bitcoinAmount = remember(amount, currency, currencies) {
74+
currency?.convert(amount.toLong())?.bitcoinDisplay(currencies.displayUnit)?.value
75+
?: amount.formatToModernDisplay()
76+
}
77+
Row(
78+
verticalAlignment = Alignment.CenterVertically,
79+
modifier = Modifier.align(Alignment.Start),
80+
) {
81+
Display(
82+
text = BITCOIN_SYMBOL,
83+
color = Colors.White,
84+
modifier = Modifier.padding(end = 6.dp),
85+
)
86+
Display(
87+
text = bitcoinAmount,
88+
color = Colors.White,
89+
)
90+
}
91+
} else {
92+
MoneySSB(
93+
sats = amount.toLong(),
94+
unit = PrimaryDisplay.BITCOIN,
95+
color = Colors.White64,
96+
showSymbol = true,
97+
modifier = Modifier.align(Alignment.Start),
98+
)
99+
VerticalSpacer(16.dp)
100+
val fiatAmount = remember(amount, currency) {
101+
currency?.convert(amount.toLong())?.formatted ?: ""
102+
}
103+
val fiatSymbol = remember(amount, currency) {
104+
currency?.convert(amount.toLong())?.symbol ?: ""
105+
}
106+
if (fiatAmount.isNotEmpty()) {
107+
Row(
108+
verticalAlignment = Alignment.CenterVertically,
109+
modifier = Modifier.align(Alignment.Start),
110+
) {
111+
Display(
112+
text = fiatSymbol,
113+
color = Colors.White,
114+
modifier = Modifier.padding(end = 6.dp),
115+
)
116+
Display(
117+
text = fiatAmount,
118+
color = Colors.White,
119+
)
120+
}
121+
}
122+
}
123+
VerticalSpacer(32.dp)
124+
125+
BodyM(
126+
text = stringResource(R.string.other__gift__claiming__text),
127+
color = Colors.White64,
128+
)
129+
130+
Spacer(modifier = Modifier.weight(1f))
131+
132+
Image(
133+
painter = painterResource(R.drawable.gift),
134+
contentDescription = null,
135+
modifier = Modifier
136+
.fillMaxWidth(IMAGE_WIDTH_FRACTION)
137+
.aspectRatio(1.0f)
138+
.align(Alignment.CenterHorizontally)
139+
)
140+
141+
VerticalSpacer(32.dp)
142+
143+
CircularProgressIndicator(
144+
modifier = Modifier
145+
.align(Alignment.CenterHorizontally)
146+
.padding(bottom = 32.dp)
147+
.testTag("GiftLoading")
148+
)
149+
}
150+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package to.bitkit.ui.sheets
2+
3+
import kotlinx.serialization.Serializable
4+
5+
internal const val IMAGE_WIDTH_FRACTION = 0.8f
6+
7+
@Serializable
8+
sealed interface GiftRoute {
9+
@Serializable
10+
data object Loading : GiftRoute
11+
12+
@Serializable
13+
data object Used : GiftRoute
14+
15+
@Serializable
16+
data object UsedUp : GiftRoute
17+
18+
@Serializable
19+
data object Error : GiftRoute
20+
21+
@Serializable
22+
data object Success : GiftRoute
23+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package to.bitkit.ui.sheets
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.imePadding
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
8+
import androidx.compose.runtime.rememberUpdatedState
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.platform.testTag
11+
import androidx.hilt.navigation.compose.hiltViewModel
12+
import androidx.navigation.compose.NavHost
13+
import androidx.navigation.compose.rememberNavController
14+
import to.bitkit.R
15+
import to.bitkit.models.NewTransactionSheetDetails
16+
import to.bitkit.ui.components.Sheet
17+
import to.bitkit.ui.shared.modifiers.sheetHeight
18+
import to.bitkit.ui.utils.composableWithDefaultTransitions
19+
import to.bitkit.viewmodels.AppViewModel
20+
21+
@Composable
22+
fun GiftSheet(
23+
sheet: Sheet.Gift,
24+
appViewModel: AppViewModel,
25+
modifier: Modifier = Modifier,
26+
viewModel: GiftViewModel = hiltViewModel(),
27+
) {
28+
val navController = rememberNavController()
29+
30+
LaunchedEffect(sheet.code, sheet.amount) {
31+
viewModel.initialize(sheet.code, sheet.amount)
32+
}
33+
34+
val onSuccessState = rememberUpdatedState { details: NewTransactionSheetDetails ->
35+
appViewModel.hideSheet()
36+
appViewModel.showNewTransactionSheet(details = details, event = null)
37+
}
38+
39+
LaunchedEffect(Unit) {
40+
viewModel.successEvent.collect { details ->
41+
onSuccessState.value(details)
42+
}
43+
}
44+
45+
LaunchedEffect(Unit) {
46+
viewModel.navigationEvent.collect { route ->
47+
when (route) {
48+
is GiftRoute.Success -> {
49+
appViewModel.hideSheet()
50+
}
51+
else -> {
52+
navController.navigate(route) {
53+
popUpTo(GiftRoute.Loading) { inclusive = false }
54+
}
55+
}
56+
}
57+
}
58+
}
59+
60+
Column(
61+
modifier = modifier
62+
.fillMaxWidth()
63+
.sheetHeight()
64+
.imePadding()
65+
.testTag("GiftSheet")
66+
) {
67+
NavHost(
68+
navController = navController,
69+
startDestination = GiftRoute.Loading,
70+
) {
71+
composableWithDefaultTransitions<GiftRoute.Loading> {
72+
GiftLoading(
73+
viewModel = viewModel,
74+
)
75+
}
76+
composableWithDefaultTransitions<GiftRoute.Used> {
77+
GiftErrorSheet(
78+
titleRes = R.string.other__gift__used__title,
79+
textRes = R.string.other__gift__used__text,
80+
testTag = "GiftUsed",
81+
onDismiss = { appViewModel.hideSheet() },
82+
)
83+
}
84+
composableWithDefaultTransitions<GiftRoute.UsedUp> {
85+
GiftErrorSheet(
86+
titleRes = R.string.other__gift__used_up__title,
87+
textRes = R.string.other__gift__used_up__text,
88+
testTag = "GiftUsedUp",
89+
onDismiss = { appViewModel.hideSheet() },
90+
)
91+
}
92+
composableWithDefaultTransitions<GiftRoute.Error> {
93+
GiftErrorSheet(
94+
titleRes = R.string.other__gift__error__title,
95+
textRes = R.string.other__gift__error__text,
96+
testTag = "GiftError",
97+
onDismiss = { appViewModel.hideSheet() },
98+
)
99+
}
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)