Skip to content

Commit 2bf4a61

Browse files
committed
Merge branch 'master' into chore/update-ldk-node
2 parents d383953 + c086a6c commit 2bf4a61

File tree

15 files changed

+1072
-244
lines changed

15 files changed

+1072
-244
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.compose.foundation.background
88
import androidx.compose.foundation.clickable
99
import androidx.compose.foundation.layout.Box
1010
import androidx.compose.foundation.layout.aspectRatio
11+
import androidx.compose.foundation.layout.fillMaxSize
1112
import androidx.compose.foundation.layout.padding
1213
import androidx.compose.foundation.layout.size
1314
import androidx.compose.foundation.shape.CircleShape
@@ -112,6 +113,13 @@ fun QrCodeImage(
112113
} else {
113114
imageComposable()
114115
}
116+
} else {
117+
Image(
118+
painter = painterResource(R.drawable.qr_placeholder),
119+
contentDescription = content,
120+
contentScale = ContentScale.Inside,
121+
modifier = Modifier.fillMaxSize()
122+
)
115123
}
116124
}
117125

app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package to.bitkit.ui.screens.wallets.activity
22

33
import androidx.activity.compose.BackHandler
4-
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
54
import androidx.compose.foundation.layout.Box
65
import androidx.compose.foundation.layout.Column
76
import androidx.compose.foundation.layout.PaddingValues
@@ -12,11 +11,7 @@ import androidx.compose.foundation.layout.padding
1211
import androidx.compose.material3.ExperimentalMaterial3Api
1312
import androidx.compose.runtime.Composable
1413
import androidx.compose.runtime.getValue
15-
import androidx.compose.runtime.remember
1614
import androidx.compose.ui.Modifier
17-
import androidx.compose.ui.composed
18-
import androidx.compose.ui.input.pointer.pointerInput
19-
import androidx.compose.ui.input.pointer.util.VelocityTracker
2015
import androidx.compose.ui.platform.testTag
2116
import androidx.compose.ui.res.stringResource
2217
import androidx.compose.ui.tooling.preview.Preview
@@ -33,6 +28,7 @@ import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter
3328
import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped
3429
import to.bitkit.ui.screens.wallets.activity.components.ActivityTab
3530
import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems
31+
import to.bitkit.ui.shared.modifiers.swipeToChangeTab
3632
import to.bitkit.ui.shared.util.screen
3733
import to.bitkit.ui.theme.AppThemeSurface
3834
import to.bitkit.viewmodels.ActivityListViewModel
@@ -146,30 +142,6 @@ private fun AllActivityScreenContent(
146142
}
147143
}
148144

149-
private fun Modifier.swipeToChangeTab(currentTabIndex: Int, tabCount: Int, onTabChange: (Int) -> Unit) = composed {
150-
val threshold = remember { 1500f }
151-
val velocityTracker = remember { VelocityTracker() }
152-
153-
pointerInput(currentTabIndex) {
154-
detectHorizontalDragGestures(
155-
onHorizontalDrag = { change, _ ->
156-
velocityTracker.addPosition(change.uptimeMillis, change.position)
157-
},
158-
onDragEnd = {
159-
val velocity = velocityTracker.calculateVelocity().x
160-
when {
161-
velocity >= threshold && currentTabIndex > 0 -> onTabChange(currentTabIndex - 1)
162-
velocity <= -threshold && currentTabIndex < tabCount - 1 -> onTabChange(currentTabIndex + 1)
163-
}
164-
velocityTracker.resetTracking()
165-
},
166-
onDragCancel = {
167-
velocityTracker.resetTracking()
168-
},
169-
)
170-
}
171-
}
172-
173145
@Preview(showSystemUi = true)
174146
@Composable
175147
private fun Preview() {

app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ fun ActivityListFilter(
100100
}
101101
}
102102

103-
enum class ActivityTab {
103+
enum class ActivityTab : TabItem {
104104
ALL, SENT, RECEIVED, OTHER;
105105

106-
val uiText: String
106+
override val uiText: String
107107
@Composable
108108
get() = when (this) {
109109
ALL -> stringResource(R.string.wallet__activity_tabs__all)

app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import androidx.compose.animation.core.FastOutSlowInEasing
55
import androidx.compose.animation.core.animateFloatAsState
66
import androidx.compose.animation.core.tween
77
import androidx.compose.foundation.background
8-
import androidx.compose.foundation.clickable
98
import androidx.compose.foundation.layout.Arrangement
109
import androidx.compose.foundation.layout.Box
1110
import androidx.compose.foundation.layout.Column
@@ -19,18 +18,21 @@ import androidx.compose.runtime.Composable
1918
import androidx.compose.runtime.getValue
2019
import androidx.compose.ui.Alignment
2120
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.graphics.Color
2222
import androidx.compose.ui.platform.testTag
2323
import androidx.compose.ui.text.style.TextOverflow
2424
import androidx.compose.ui.unit.dp
2525
import to.bitkit.ui.components.CaptionB
26+
import to.bitkit.ui.shared.util.clickableAlpha
2627
import to.bitkit.ui.theme.Colors
2728

2829
@Composable
29-
fun CustomTabRowWithSpacing(
30-
tabs: List<ActivityTab>,
30+
fun <T : TabItem> CustomTabRowWithSpacing(
31+
tabs: List<T>,
3132
currentTabIndex: Int,
32-
onTabChange: (ActivityTab) -> Unit,
33+
onTabChange: (T) -> Unit,
3334
modifier: Modifier = Modifier,
35+
selectedColor: Color = Colors.Brand,
3436
) {
3537
Column(modifier = modifier) {
3638
Row(
@@ -47,7 +49,7 @@ fun CustomTabRowWithSpacing(
4749
contentAlignment = Alignment.Center,
4850
modifier = Modifier
4951
.fillMaxWidth()
50-
.clickable { onTabChange(tab) }
52+
.clickableAlpha { onTabChange(tab) }
5153
.padding(vertical = 8.dp)
5254
.testTag("Tab-${tab.name.lowercase()}"),
5355
) {
@@ -59,16 +61,21 @@ fun CustomTabRowWithSpacing(
5961
)
6062
}
6163

62-
// Animated indicator
6364
val animatedAlpha by animateFloatAsState(
6465
targetValue = if (isSelected) 1f else 0.2f,
65-
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
66+
animationSpec = tween(
67+
durationMillis = 250,
68+
easing = FastOutSlowInEasing
69+
),
6670
label = "indicatorAlpha"
6771
)
6872

6973
val animatedColor by animateColorAsState(
70-
targetValue = if (isSelected) Colors.Brand else Colors.White,
71-
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
74+
targetValue = if (isSelected) selectedColor else Colors.White,
75+
animationSpec = tween(
76+
durationMillis = 250,
77+
easing = FastOutSlowInEasing
78+
),
7279
label = "indicatorColor"
7380
)
7481

@@ -88,3 +95,9 @@ fun CustomTabRowWithSpacing(
8895
}
8996
}
9097
}
98+
99+
interface TabItem {
100+
val name: String
101+
val uiText: String
102+
@Composable get
103+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package to.bitkit.ui.screens.wallets.receive
2+
3+
import to.bitkit.R
4+
5+
/**
6+
* Returns the appropriate invoice/address for the selected tab.
7+
*
8+
* @param tab The selected receive tab
9+
* @param bip21 Full BIP21 invoice (onchain + lightning)
10+
* @param bolt11 Lightning invoice
11+
* @param cjitInvoice CJIT invoice from Blocktank (if active)
12+
* @param onchainAddress Pure Bitcoin address (fallback)
13+
* @return The invoice string to display/encode in QR
14+
*/
15+
fun getInvoiceForTab(
16+
tab: ReceiveTab,
17+
bip21: String,
18+
bolt11: String,
19+
cjitInvoice: String?,
20+
isNodeRunning: Boolean,
21+
onchainAddress: String,
22+
): String {
23+
return when (tab) {
24+
ReceiveTab.SAVINGS -> {
25+
// Return BIP21 without lightning parameter to preserve amount and other parameters
26+
removeLightningFromBip21(bip21, onchainAddress)
27+
}
28+
29+
ReceiveTab.AUTO -> {
30+
bip21.takeIf { isNodeRunning && containsLightningParameter(bip21) }.orEmpty()
31+
}
32+
33+
ReceiveTab.SPENDING -> {
34+
// Lightning only: prefer CJIT > bolt11
35+
cjitInvoice?.takeIf { it.isNotEmpty() && isNodeRunning }
36+
?: bolt11
37+
}
38+
}
39+
}
40+
41+
/**
42+
* Removes the lightning parameter from a BIP21 URI while preserving all other parameters.
43+
*
44+
* @param bip21 Full BIP21 URI (e.g., bitcoin:address?amount=0.001&lightning=lnbc...)
45+
* @param fallbackAddress Fallback address if BIP21 is empty or invalid
46+
* @return BIP21 URI without the lightning parameter (e.g., bitcoin:address?amount=0.001)
47+
*/
48+
fun removeLightningFromBip21(bip21: String, fallbackAddress: String): String {
49+
if (bip21.isBlank()) return fallbackAddress
50+
51+
// Remove lightning parameter using regex
52+
// Handles both "?lightning=..." and "&lightning=..." cases
53+
val withoutLightning = bip21
54+
.replace(Regex("[?&]lightning=[^&]*"), "")
55+
.replace(Regex("\\?$"), "") // Remove trailing ? if it's the last char
56+
57+
return withoutLightning.ifBlank { fallbackAddress }
58+
}
59+
60+
/**
61+
* Checks if a BIP21 URI contains a lightning parameter.
62+
*
63+
* @param bip21 The BIP21 URI to check
64+
* @return true if the URI contains a lightning parameter, false otherwise
65+
*/
66+
private fun containsLightningParameter(bip21: String): Boolean {
67+
return Regex("[?&]lightning=[^&]*").containsMatchIn(bip21)
68+
}
69+
70+
/**
71+
* Returns the appropriate QR code logo resource for the selected tab.
72+
*
73+
* @param tab The selected receive tab
74+
* @return Drawable resource ID for QR logo
75+
*/
76+
fun getQrLogoResource(tab: ReceiveTab): Int {
77+
return when (tab) {
78+
ReceiveTab.SAVINGS -> R.drawable.ic_btc_circle
79+
ReceiveTab.AUTO -> R.drawable.ic_unified_circle
80+
ReceiveTab.SPENDING -> R.drawable.ic_ln_circle
81+
}
82+
}

0 commit comments

Comments
 (0)