Skip to content

Commit 95c7f77

Browse files
committed
fix: polish public contact payments
1 parent 50e1552 commit 95c7f77

9 files changed

Lines changed: 69 additions & 16 deletions

File tree

app/src/main/java/to/bitkit/ext/Activities.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ fun Activity.isTransfer() = this is Activity.Onchain && this.v1.isTransfer
6666

6767
fun Activity.doesExist() = this is Activity.Onchain && this.v1.doesExist
6868

69+
fun Activity.isReplacedSentTransaction(txIdsInBoostTxIds: Set<String>): Boolean =
70+
this is Activity.Onchain &&
71+
!v1.doesExist &&
72+
v1.txType == PaymentType.SENT &&
73+
v1.txId in txIdsInBoostTxIds
74+
6975
fun Activity.paymentState(): PaymentState? = when (this) {
7076
is Activity.Lightning -> this.v1.status
7177
is Activity.Onchain -> null

app/src/main/java/to/bitkit/repositories/ActivityRepo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import to.bitkit.di.BgDispatcher
3636
import to.bitkit.di.IoDispatcher
3737
import to.bitkit.ext.amountOnClose
3838
import to.bitkit.ext.contact
39+
import to.bitkit.ext.isReplacedSentTransaction
3940
import to.bitkit.ext.matchesPaymentId
4041
import to.bitkit.ext.nowMillis
4142
import to.bitkit.ext.nowTimestamp
@@ -345,10 +346,13 @@ class ActivityRepo @Inject constructor(
345346
suspend fun contactActivities(publicKey: String): Result<List<Activity>> = withContext(ioDispatcher) {
346347
runCatching {
347348
val normalizedKey = PubkyPublicKeyFormat.normalized(publicKey) ?: publicKey
349+
val txIdsInBoostTxIds = getTxIdsInBoostTxIds()
348350
getActivities(
349351
filter = ActivityFilter.ALL,
350352
sortDirection = SortDirection.DESC,
351-
).getOrThrow().filter { PubkyPublicKeyFormat.matches(it.contact(), normalizedKey) }
353+
).getOrThrow()
354+
.filterNot { it.isReplacedSentTransaction(txIdsInBoostTxIds) }
355+
.filter { PubkyPublicKeyFormat.matches(it.contact(), normalizedKey) }
352356
}.onFailure {
353357
Logger.error("Failed to load contact activities for '$publicKey'", it, context = TAG)
354358
}

app/src/main/java/to/bitkit/services/CoreService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,8 @@ class ActivityService(
10211021
val updatedActivity = replacementActivity.copy(
10221022
boostTxIds = replacementActivity.boostTxIds + txid,
10231023
isBoosted = true,
1024-
updatedAt = System.currentTimeMillis().toULong() / 1000u
1024+
contact = replacementActivity.contact ?: replacedActivity?.contact,
1025+
updatedAt = System.currentTimeMillis().toULong() / 1000u,
10251026
)
10261027
updateActivity(replacementActivity.id, Activity.Onchain(updatedActivity))
10271028

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,7 @@ private fun NavGraphBuilder.contacts(
10051005
onBackClick = { navController.popBackStack() },
10061006
onContactSaved = { navController.popBackStack() },
10071007
onPayContact = { paymentRequest, publicKey ->
1008+
navController.popBackStack()
10081009
appViewModel.openContactPayment(paymentRequest, publicKey)
10091010
},
10101011
)

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.stateIn
2626
import kotlinx.coroutines.flow.update
2727
import kotlinx.coroutines.launch
2828
import to.bitkit.di.BgDispatcher
29+
import to.bitkit.ext.isReplacedSentTransaction
2930
import to.bitkit.ext.isTransfer
3031
import to.bitkit.models.PubkyProfile
3132
import to.bitkit.repositories.ActivityRepo
@@ -146,19 +147,7 @@ class ActivityListViewModel @Inject constructor(
146147

147148
private suspend fun filterOutReplacedSentTransactions(activities: List<Activity>): List<Activity> {
148149
val txIdsInBoostTxIds = activityRepo.getTxIdsInBoostTxIds()
149-
150-
return activities.filter {
151-
if (it is Activity.Onchain) {
152-
val onchain = it.v1
153-
if (!onchain.doesExist &&
154-
onchain.txType == PaymentType.SENT &&
155-
txIdsInBoostTxIds.contains(onchain.txId)
156-
) {
157-
return@filter false
158-
}
159-
}
160-
true
161-
}
150+
return activities.filterNot { it.isReplacedSentTransaction(txIdsInBoostTxIds) }
162151
}
163152

164153
fun updateAvailableTags() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ class AppViewModel @Inject constructor(
11371137
}
11381138

11391139
private fun onAddressContinue(data: String) {
1140-
launchScan(source = ScanSource.ADDRESS_CONTINUE, data = data)
1140+
launchScan(source = ScanSource.ADDRESS_CONTINUE, data = data, routePubkyKeys = true)
11411141
}
11421142

11431143
private suspend fun onAmountChange(amount: ULong) {

app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class ActivityRepoTest : BaseUnitTest() {
8787
confirmTimestamp: ULong? = baseOnchainActivity.confirmTimestamp,
8888
channelId: String? = baseOnchainActivity.channelId,
8989
transferTxId: String? = baseOnchainActivity.transferTxId,
90+
contact: String? = baseOnchainActivity.contact,
9091
createdAt: ULong? = baseOnchainActivity.createdAt,
9192
updatedAt: ULong? = baseOnchainActivity.updatedAt,
9293
): Activity.Onchain {
@@ -107,6 +108,7 @@ class ActivityRepoTest : BaseUnitTest() {
107108
confirmTimestamp = confirmTimestamp,
108109
channelId = channelId,
109110
transferTxId = transferTxId,
111+
contact = contact,
110112
createdAt = createdAt,
111113
updatedAt = updatedAt
112114
)
@@ -267,6 +269,41 @@ class ActivityRepoTest : BaseUnitTest() {
267269
assertNull(result.getOrThrow())
268270
}
269271

272+
@Test
273+
fun `contactActivities filters replaced sent transaction`() = test {
274+
val contactPublicKey = "pubky3rsduhcxpw74snwyct86m38c63j3pq8x4ycqikxg64roik8yw5xg"
275+
val replacedTxId = "replaced_tx_id"
276+
val replacedActivity = createOnchainActivity(
277+
id = "replaced_activity_id",
278+
txId = replacedTxId,
279+
doesExist = false,
280+
contact = contactPublicKey,
281+
)
282+
val replacementActivity = createOnchainActivity(
283+
id = "replacement_activity_id",
284+
txId = "replacement_tx_id",
285+
boostTxIds = listOf(replacedTxId),
286+
contact = contactPublicKey,
287+
)
288+
whenever(coreService.activity.getTxIdsInBoostTxIds()).thenReturn(setOf(replacedTxId))
289+
whenever(
290+
coreService.activity.get(
291+
filter = ActivityFilter.ALL,
292+
txType = null,
293+
tags = null,
294+
search = null,
295+
minDate = null,
296+
maxDate = null,
297+
limit = null,
298+
sortDirection = SortDirection.DESC,
299+
)
300+
).thenReturn(listOf(replacedActivity, replacementActivity))
301+
302+
val result = sut.contactActivities(contactPublicKey)
303+
304+
assertEquals(listOf(replacementActivity), result.getOrThrow())
305+
}
306+
270307
@Test
271308
fun `updateActivity updates successfully when not deleted`() = test {
272309
val activityId = "activity123"

app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package to.bitkit.viewmodels
22

33
import android.content.Context
4+
import app.cash.turbine.test
45
import com.synonym.bitkitcore.LightningInvoice
56
import com.synonym.bitkitcore.NetworkType
67
import com.synonym.bitkitcore.Scanner
@@ -50,6 +51,7 @@ import to.bitkit.services.AppUpdaterService
5051
import to.bitkit.services.CoreService
5152
import to.bitkit.services.MigrationService
5253
import to.bitkit.test.BaseUnitTest
54+
import to.bitkit.ui.Routes
5355
import to.bitkit.ui.components.Sheet
5456
import to.bitkit.ui.shared.toast.ToastQueueManager
5557
import to.bitkit.ui.sheets.SendRoute
@@ -93,6 +95,7 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
9395
private val settingsData = MutableStateFlow(SettingsData())
9496
private val walletState = MutableStateFlow(WalletState())
9597
private val nodeEvents = MutableSharedFlow<Event>()
98+
private val testPublicKey = "pubky3rsduhcxpw74snwyct86m38c63j3pq8x4ycqikxg64roik8yw5xg"
9699

97100
private val timedSheetManager = mock<TimedSheetManager>()
98101

@@ -123,6 +126,8 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
123126
whenever { widgetsRepo.refreshEnabledWidgets() }.thenReturn(Unit)
124127
whenever { lightningRepo.updateGeoBlockState() }.thenReturn(Unit)
125128
whenever(pubkyRepo.sessionRestorationFailed).thenReturn(MutableStateFlow(false))
129+
whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow(null))
130+
whenever(pubkyRepo.contacts).thenReturn(MutableStateFlow(emptyList()))
126131
whenever(currencyRepo.convertSatsToFiat(any(), anyOrNull()))
127132
.thenReturn(Result.failure(Exception("not mocked")))
128133
whenever { lightningRepo.calculateTotalFee(any(), anyOrNull(), any(), anyOrNull(), anyOrNull()) }
@@ -218,6 +223,15 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
218223
assertFalse(sut.sendUiState.value.canSwitchWallet)
219224
}
220225

226+
@Test
227+
fun `manual address continue routes pubky to add contact`() = test {
228+
sut.mainScreenEffect.test {
229+
sut.setSendEvent(SendEvent.AddressContinue(testPublicKey))
230+
231+
assertEquals(MainScreenEffect.Navigate(Routes.AddContact(testPublicKey)), awaitItem())
232+
}
233+
}
234+
221235
@Test
222236
fun `canSwitchWallet is false when amount exceeds lightning balance`() = test {
223237
balanceState.value = BalanceState(

changelog.d/next/931.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved public contact payment flows for manual Pubky entry, add-contact payments, and RBF activity display.

0 commit comments

Comments
 (0)