Skip to content

Commit 32ff9b4

Browse files
committed
chore: ReceiveInvoiceUtils
1 parent 961ca84 commit 32ff9b4

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package to.bitkit.ui.screens.wallets.receive
2+
3+
import to.bitkit.R
4+
import to.bitkit.models.ChannelDetails
5+
import to.bitkit.models.NodeLifecycleState
6+
7+
/**
8+
* Strips the Lightning invoice parameter from a BIP21 URI, returning pure onchain address.
9+
*
10+
* Example:
11+
* Input: "bitcoin:bc1q...?amount=0.001&lightning=lnbc..."
12+
* Output: "bitcoin:bc1q...?amount=0.001"
13+
*
14+
* @param bip21 The full BIP21 URI
15+
* @return BIP21 URI without lightning parameter
16+
*/
17+
fun stripLightningFromBip21(bip21: String): String {
18+
if (bip21.isEmpty()) return bip21
19+
20+
// Remove lightning parameter and its value
21+
// Pattern: &lightning=... or ?lightning=...
22+
val lightningParamRegex = Regex("[?&]lightning=[^&]*")
23+
var result = bip21.replace(lightningParamRegex, "")
24+
25+
// If we removed the first param (started with ?), convert & to ?
26+
if (result.contains("&") && !result.contains("?")) {
27+
result = result.replaceFirst("&", "?")
28+
}
29+
30+
// Clean up trailing ? or &
31+
result = result.trimEnd('?', '&')
32+
33+
return result
34+
}
35+
36+
/**
37+
* Returns the appropriate invoice/address for the selected tab.
38+
*
39+
* @param tab The selected receive tab
40+
* @param bip21 Full BIP21 invoice (onchain + lightning)
41+
* @param bolt11 Lightning invoice
42+
* @param cjitInvoice CJIT invoice from Blocktank (if active)
43+
* @param onchainAddress Pure Bitcoin address (fallback)
44+
* @return The invoice string to display/encode in QR
45+
*/
46+
fun getInvoiceForTab(
47+
tab: ReceiveTab,
48+
bip21: String,
49+
bolt11: String,
50+
cjitInvoice: String?,
51+
onchainAddress: String
52+
): String {
53+
return when (tab) {
54+
ReceiveTab.SAVINGS -> {
55+
// Pure onchain: strip lightning from BIP21
56+
val strippedBip21 = stripLightningFromBip21(bip21)
57+
strippedBip21.ifEmpty { onchainAddress }
58+
}
59+
ReceiveTab.AUTO -> {
60+
// Unified: prefer CJIT > full BIP21
61+
cjitInvoice?.takeIf { it.isNotEmpty() }
62+
?: bip21.ifEmpty { onchainAddress }
63+
}
64+
ReceiveTab.SPENDING -> {
65+
// Lightning only: prefer CJIT > bolt11
66+
cjitInvoice?.takeIf { it.isNotEmpty() }
67+
?: bolt11.ifEmpty { onchainAddress }
68+
}
69+
}
70+
}
71+
72+
/**
73+
* Returns the appropriate QR code logo resource for the selected tab.
74+
*
75+
* @param tab The selected receive tab
76+
* @param hasCjit Whether a CJIT invoice is active
77+
* @return Drawable resource ID for QR logo
78+
*/
79+
fun getQrLogoResource(tab: ReceiveTab, hasCjit: Boolean): Int {
80+
return when (tab) {
81+
ReceiveTab.SAVINGS -> R.drawable.ic_btc_circle
82+
ReceiveTab.AUTO -> {
83+
// Unified logo if CJIT or standard unified
84+
if (hasCjit) R.drawable.ic_unified_circle
85+
else R.drawable.ic_unified_circle
86+
}
87+
ReceiveTab.SPENDING -> R.drawable.ic_ln_circle
88+
}
89+
}
90+
91+
/**
92+
* Determines whether the Auto (unified) tab should be visible.
93+
*
94+
* Logic:
95+
* - Node must be running
96+
* - If geoblocked: only show if user has existing channels (grandfathered)
97+
* - If not geoblocked: always show
98+
*
99+
* @param channels List of Lightning channels
100+
* @param isGeoblocked Whether Lightning is geoblocked for this user
101+
* @param nodeRunning Whether the Lightning node is running
102+
* @return true if Auto tab should be visible
103+
*/
104+
fun shouldShowAutoTab(
105+
channels: List<ChannelDetails>,
106+
isGeoblocked: Boolean,
107+
nodeRunning: Boolean
108+
): Boolean {
109+
if (!nodeRunning) return false
110+
111+
return if (isGeoblocked) {
112+
// Geoblocked users can still use Auto if they have existing channels
113+
channels.isNotEmpty()
114+
} else {
115+
// Not geoblocked: always show Auto tab
116+
true
117+
}
118+
}
119+
120+
/**
121+
* Extension: Check if node lifecycle state is running.
122+
*/
123+
fun NodeLifecycleState.isRunning(): Boolean {
124+
return this == NodeLifecycleState.Running
125+
}
126+
127+
/**
128+
* Extension: Check if node lifecycle state is starting.
129+
*/
130+
fun NodeLifecycleState.isStarting(): Boolean {
131+
return this == NodeLifecycleState.Starting
132+
}

0 commit comments

Comments
 (0)