Skip to content

Commit 05d12bb

Browse files
authored
Merge pull request #390 from synonymdev/feat/deeplinks
Deeplinks
2 parents a849229 + 8701bb4 commit 05d12bb

File tree

5 files changed

+197
-39
lines changed

5 files changed

+197
-39
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,57 @@
4646

4747
<category android:name="android.intent.category.LAUNCHER" />
4848
</intent-filter>
49+
50+
<!-- Universal Links -->
51+
<intent-filter android:autoVerify="true">
52+
<action android:name="android.intent.action.VIEW" />
53+
<category android:name="android.intent.category.DEFAULT" />
54+
<category android:name="android.intent.category.BROWSABLE" />
55+
<data
56+
android:scheme="https"
57+
android:host="www.bitkit.to"
58+
android:pathPattern="/treasure-hunt" />
59+
</intent-filter>
60+
61+
<!-- Deeplinks -->
62+
<intent-filter>
63+
<action android:name="android.intent.action.VIEW" />
64+
<category android:name="android.intent.category.DEFAULT" />
65+
<category android:name="android.intent.category.BROWSABLE" />
66+
<data android:scheme="bitkit" />
67+
<data android:scheme="slash" />
68+
<data android:scheme="slashauth" />
69+
<data android:scheme="bitcoin" />
70+
<data android:scheme="BITCOIN"
71+
tools:ignore="AppLinkUrlError" />
72+
<data android:scheme="lightning" />
73+
<data android:scheme="LIGHTNING"
74+
tools:ignore="AppLinkUrlError" />
75+
<data android:scheme="lnurl" />
76+
<data android:scheme="lnurlw" />
77+
<data android:scheme="lnurlc" />
78+
<data android:scheme="lnurlp" />
79+
</intent-filter>
80+
81+
<!-- NFC -->
82+
<intent-filter>
83+
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
84+
<category android:name="android.intent.category.DEFAULT" />
85+
<category android:name="android.intent.category.BROWSABLE" />
86+
<data android:scheme="bitkit" />
87+
<data android:scheme="slash" />
88+
<data android:scheme="slashauth" />
89+
<data android:scheme="bitcoin" />
90+
<data android:scheme="BITCOIN"
91+
tools:ignore="AppLinkUrlError" />
92+
<data android:scheme="lightning" />
93+
<data android:scheme="LIGHTNING"
94+
tools:ignore="AppLinkUrlError" />
95+
<data android:scheme="lnurl" />
96+
<data android:scheme="lnurlw" />
97+
<data android:scheme="lnurlc" />
98+
<data android:scheme="lnurlp" />
99+
</intent-filter>
49100
</activity>
50101

51102
<service

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class MainActivity : FragmentActivity() {
8080
startForegroundService(Intent(this, LightningNodeService::class.java))
8181
installSplashScreen()
8282
enableAppEdgeToEdge()
83+
appViewModel.handleDeeplinkIntent(intent)
8384
setContent {
8485
AppThemeSurface(
8586
modifier = Modifier.semantics {
@@ -160,6 +161,13 @@ class MainActivity : FragmentActivity() {
160161
SplashScreen(appViewModel.splashVisible)
161162
}
162163
}
164+
165+
}
166+
167+
override fun onNewIntent(intent: Intent) {
168+
super.onNewIntent(intent)
169+
setIntent(intent)
170+
appViewModel.handleDeeplinkIntent(intent)
163171
}
164172
}
165173

app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt

Lines changed: 107 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import to.bitkit.ui.components.Caption13Up
6262
import to.bitkit.ui.components.FillHeight
6363
import to.bitkit.ui.components.PrimaryButton
6464
import to.bitkit.ui.components.SwipeToConfirm
65+
import to.bitkit.ui.components.SyncNodeView
6566
import to.bitkit.ui.components.TagButton
6667
import to.bitkit.ui.components.TextInput
6768
import to.bitkit.ui.components.VerticalSpacer
@@ -88,6 +89,7 @@ import java.time.Instant
8889
fun SendConfirmScreen(
8990
savedStateHandle: SavedStateHandle,
9091
uiState: SendUiState,
92+
isNodeRunning: Boolean,
9193
canGoBack: Boolean,
9294
onBack: () -> Unit,
9395
onEvent: (SendEvent) -> Unit,
@@ -133,6 +135,7 @@ fun SendConfirmScreen(
133135

134136
Content(
135137
uiState = uiState,
138+
isNodeRunning = isNodeRunning,
136139
isLoading = isLoading,
137140
showBiometrics = showBiometrics,
138141
canGoBack = canGoBack,
@@ -163,6 +166,7 @@ fun SendConfirmScreen(
163166
@Composable
164167
private fun Content(
165168
uiState: SendUiState,
169+
isNodeRunning: Boolean,
166170
isLoading: Boolean,
167171
showBiometrics: Boolean,
168172
modifier: Modifier = Modifier,
@@ -194,47 +198,22 @@ private fun Content(
194198

195199
Spacer(Modifier.height(16.dp))
196200

197-
Column(
198-
modifier = Modifier
199-
.padding(horizontal = 16.dp)
200-
.fillMaxSize()
201-
.verticalScroll(rememberScrollState())
202-
) {
203-
BalanceHeaderView(
204-
sats = uiState.amount.toLong(),
205-
useSwipeToHide = false,
206-
onClick = { onEvent(SendEvent.BackToAmount) },
201+
if (isNodeRunning) {
202+
ContentRunning(
203+
uiState = uiState,
204+
onEvent = onEvent,
205+
isLoading = isLoading,
206+
onClickAddTag = onClickAddTag,
207+
onClickTag = onClickTag,
208+
onSwipeToConfirm = onSwipeToConfirm,
209+
)
210+
} else {
211+
SyncNodeView(
207212
modifier = Modifier
208213
.fillMaxWidth()
209-
.testTag("ReviewAmount"),
210-
testTag = "ReviewAmount"
211-
)
212-
213-
Spacer(modifier = Modifier.height(16.dp))
214-
215-
when (uiState.payMethod) {
216-
SendMethod.ONCHAIN -> OnChainDescription(uiState = uiState, onEvent = onEvent)
217-
SendMethod.LIGHTNING -> LightningDescription(uiState = uiState, onEvent = onEvent)
218-
}
219-
220-
if (isLnurlPay) {
221-
if (uiState.lnurl.data.commentAllowed()) {
222-
LnurlCommentSection(uiState, onEvent)
223-
}
224-
} else {
225-
TagsSection(uiState, onClickTag, onClickAddTag)
226-
}
227-
228-
FillHeight()
229-
VerticalSpacer(16.dp)
230-
231-
SwipeToConfirm(
232-
text = stringResource(R.string.wallet__send_swipe),
233-
loading = isLoading,
234-
confirmed = isLoading,
235-
onConfirm = onSwipeToConfirm,
214+
.weight(1f)
215+
.testTag("sync_node_view")
236216
)
237-
VerticalSpacer(16.dp)
238217
}
239218
}
240219

@@ -264,6 +243,59 @@ private fun Content(
264243
}
265244
}
266245

246+
@Composable
247+
fun ContentRunning(
248+
uiState: SendUiState,
249+
onEvent: (SendEvent) -> Unit,
250+
isLoading: Boolean,
251+
onClickAddTag: () -> Unit,
252+
onClickTag: (String) -> Unit,
253+
onSwipeToConfirm: () -> Unit,
254+
) {
255+
Column(
256+
modifier = Modifier
257+
.padding(horizontal = 16.dp)
258+
.fillMaxSize()
259+
.verticalScroll(rememberScrollState())
260+
) {
261+
BalanceHeaderView(
262+
sats = uiState.amount.toLong(),
263+
useSwipeToHide = false,
264+
onClick = { onEvent(SendEvent.BackToAmount) },
265+
modifier = Modifier
266+
.fillMaxWidth()
267+
.testTag("ReviewAmount"),
268+
testTag = "ReviewAmount"
269+
)
270+
271+
Spacer(modifier = Modifier.height(16.dp))
272+
273+
when (uiState.payMethod) {
274+
SendMethod.ONCHAIN -> OnChainDescription(uiState = uiState, onEvent = onEvent)
275+
SendMethod.LIGHTNING -> LightningDescription(uiState = uiState, onEvent = onEvent)
276+
}
277+
278+
if (uiState.lnurl is LnurlParams.LnurlPay) {
279+
if (uiState.lnurl.data.commentAllowed()) {
280+
LnurlCommentSection(uiState, onEvent)
281+
}
282+
} else {
283+
TagsSection(uiState, onClickTag, onClickAddTag)
284+
}
285+
286+
FillHeight()
287+
VerticalSpacer(16.dp)
288+
289+
SwipeToConfirm(
290+
text = stringResource(R.string.wallet__send_swipe),
291+
loading = isLoading,
292+
confirmed = isLoading,
293+
onConfirm = onSwipeToConfirm,
294+
)
295+
VerticalSpacer(16.dp)
296+
}
297+
}
298+
267299
@Composable
268300
private fun LnurlCommentSection(
269301
uiState: SendUiState,
@@ -574,6 +606,7 @@ private fun PreviewOnChain() {
574606
fee = SendFee.OnChain(1_234),
575607
speed = TransactionSpeed.Medium,
576608
),
609+
isNodeRunning = true,
577610
isLoading = false,
578611
showBiometrics = false,
579612
modifier = Modifier.sheetHeight(),
@@ -594,6 +627,7 @@ private fun PreviewOnChainLongFeeSmallScreen() {
594627
fee = SendFee.OnChain(654_321),
595628
speed = TransactionSpeed.Custom(12_345u),
596629
),
630+
isNodeRunning = true,
597631
isLoading = false,
598632
showBiometrics = false,
599633
modifier = Modifier.sheetHeight(),
@@ -612,6 +646,7 @@ private fun PreviewOnChainFeeLoading() {
612646
selectedTags = listOf("car", "house", "uber"),
613647
fee = null,
614648
),
649+
isNodeRunning = true,
615650
isLoading = false,
616651
showBiometrics = false,
617652
modifier = Modifier.sheetHeight(),
@@ -632,6 +667,7 @@ private fun PreviewLightning() {
632667
selectedTags = emptyList(),
633668
fee = SendFee.Lightning(43),
634669
),
670+
isNodeRunning = true,
635671
isLoading = false,
636672
showBiometrics = false,
637673
modifier = Modifier.sheetHeight(),
@@ -661,6 +697,7 @@ private fun PreviewLnurl() {
661697
),
662698
),
663699
),
700+
isNodeRunning = true,
664701
isLoading = false,
665702
showBiometrics = false,
666703
modifier = Modifier.sheetHeight(),
@@ -676,6 +713,7 @@ private fun PreviewBio() {
676713
BottomSheetPreview {
677714
Content(
678715
uiState = sendUiState(),
716+
isNodeRunning = true,
679717
isLoading = false,
680718
showBiometrics = true,
681719
modifier = Modifier.sheetHeight(),
@@ -693,10 +731,41 @@ private fun PreviewDialog() {
693731
uiState = sendUiState().copy(
694732
showSanityWarningDialog = SanityWarning.VALUE_OVER_100_USD,
695733
),
734+
isNodeRunning = true,
696735
isLoading = false,
697736
showBiometrics = true,
698737
modifier = Modifier.sheetHeight(),
699738
)
700739
}
701740
}
702741
}
742+
743+
@Preview(showSystemUi = true)
744+
@Composable
745+
private fun PreviewNodeNotRunning() {
746+
AppThemeSurface {
747+
BottomSheetPreview {
748+
Content(
749+
uiState = sendUiState().copy(
750+
payMethod = SendMethod.LIGHTNING,
751+
lnurl = LnurlParams.LnurlPay(
752+
data = LnurlPayData(
753+
uri = "veryLongLnurlPayUri12345677890123456789012345678901234567890",
754+
callback = "",
755+
metadataStr = "",
756+
commentAllowed = 255u,
757+
minSendable = 1000u,
758+
maxSendable = 1000_000u,
759+
allowsNostr = false,
760+
nostrPubkey = null,
761+
),
762+
),
763+
),
764+
isNodeRunning = false,
765+
isLoading = false,
766+
showBiometrics = false,
767+
modifier = Modifier.sheetHeight(),
768+
)
769+
}
770+
}
771+
}

app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,12 @@ fun SendSheet(
167167
}
168168
composableWithDefaultTransitions<SendRoute.Confirm> {
169169
val uiState by appViewModel.sendUiState.collectAsStateWithLifecycle()
170+
val walletUiState by walletViewModel.uiState.collectAsStateWithLifecycle()
171+
170172
SendConfirmScreen(
171173
savedStateHandle = it.savedStateHandle,
172174
uiState = uiState,
175+
isNodeRunning = walletUiState.nodeLifecycleState.isRunning(),
173176
canGoBack = startDestination != SendRoute.Confirm,
174177
onBack = {
175178
if (!navController.popBackStack()) {

app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package to.bitkit.viewmodels
22

33
import android.content.Context
4+
import android.content.Intent
5+
import android.net.Uri
46
import androidx.annotation.StringRes
57
import androidx.compose.runtime.getValue
68
import androidx.compose.runtime.mutableStateOf
@@ -901,7 +903,7 @@ class AppViewModel @Inject constructor(
901903
}
902904

903905
private suspend fun proceedWithPayment() {
904-
delay(300) // wait for screen transitions when applicable
906+
delay(SCREEN_TRANSITION_DELAY_MS) // wait for screen transitions when applicable
905907

906908
val amount = _sendUiState.value.amount
907909

@@ -1496,12 +1498,37 @@ class AppViewModel @Inject constructor(
14961498
setSendEffect(SendEffect.PaymentSuccess(details))
14971499
}
14981500

1501+
fun handleDeeplinkIntent(intent: Intent) {
1502+
intent.data?.let { uri ->
1503+
Logger.debug("Received deeplink: $uri")
1504+
processDeeplink(uri)
1505+
}
1506+
}
1507+
1508+
private fun processDeeplink(uri: Uri) = viewModelScope.launch {
1509+
if (!walletRepo.walletExists()) return@launch
1510+
1511+
val data = uri.toString()
1512+
delay(SCREEN_TRANSITION_DELAY_MS)
1513+
handleScan(data.removeLightningSchemes())
1514+
}
1515+
1516+
// Todo Temporaary fix while these schemes can't be decoded
1517+
private fun String.removeLightningSchemes(): String {
1518+
return this
1519+
.replace("lnurl:", "")
1520+
.replace("lnurlw:", "")
1521+
.replace("lnurlc:", "")
1522+
.replace("lnurlp:", "")
1523+
}
1524+
14991525
companion object {
15001526
private const val TAG = "AppViewModel"
15011527
private const val SEND_AMOUNT_WARNING_THRESHOLD = 100.0
15021528
private const val TEN_USD = 10
15031529
private const val MAX_BALANCE_FRACTION = 0.5
15041530
private const val MAX_FEE_AMOUNT_RATIO = 0.5
1531+
private const val SCREEN_TRANSITION_DELAY_MS = 300L
15051532
}
15061533
}
15071534

0 commit comments

Comments
 (0)