Skip to content

Commit 2d84b98

Browse files
committed
feat(flipcash/balance): implement pending transfer cancellation from feed
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 2ecee8c commit 2d84b98

4 files changed

Lines changed: 94 additions & 6 deletions

File tree

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,13 @@
3737

3838
<string name="subtitle_allowCameraAccess">Code enables you to grab Digital Cash by pointing your camera at the digital bill on another user\'s phone</string>
3939
<string name="subtitle_allowCameraSettings">You need to allow camera access to be able to grab Digital Cash</string>
40+
41+
<string name="error_title_failedToCancelTransfer">Something went wrong</string>
42+
<string name="error_description_failedToCancelTransfer">We were unable to cancel your transfer. Please try again</string>
43+
44+
<string name="prompt_title_cancelTransferNoAmount">Cancel Transfer?</string>
45+
<string name="prompt_title_cancelTransferWithAmount">Cancel %1$s Transfer?</string>
46+
<string name="prompt_description_cancelTransfer">The money will be returned to your wallet</string>
47+
<string name="action_cancelTransfer">Cancel Transfer</string>
48+
<string name="action_nevermind">Nevermind</string>
4049
</resources>

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceScreenContent.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ internal fun BalanceScreenContent(viewModel: BalanceViewModel) {
5353
modifier = Modifier
5454
.fillParentMaxWidth()
5555
.animateItem(),
56-
message = message
56+
message = message,
57+
onCancelRequested = {
58+
viewModel.dispatchEvent(BalanceViewModel.Event.OnCancelRequested(message))
59+
}
5760
)
5861

5962
if (index < state.feed.lastIndex) {

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@ package com.flipcash.app.balance.internal
22

33
import androidx.lifecycle.viewModelScope
44
import com.flipcash.app.core.extensions.onResult
5+
import com.flipcash.app.core.money.formatted
6+
import com.flipcash.features.balance.R
57
import com.flipcash.services.controllers.ActivityFeedController
68
import com.flipcash.services.models.ActivityFeedMessage
79
import com.flipcash.services.models.ActivityFeedType
10+
import com.flipcash.services.models.FeedMessageMetadata
11+
import com.flipcash.services.user.UserManager
12+
import com.getcode.manager.BottomBarManager
13+
import com.getcode.manager.TopBarManager
814
import com.getcode.opencode.controllers.BalanceController
15+
import com.getcode.opencode.controllers.TransactionController
916
import com.getcode.opencode.model.financial.LocalFiat
17+
import com.getcode.solana.keys.PublicKey
18+
import com.getcode.util.resources.ResourceHelper
1019
import com.getcode.view.BaseViewModel2
1120
import dagger.hilt.android.lifecycle.HiltViewModel
1221
import kotlinx.coroutines.flow.filterIsInstance
1322
import kotlinx.coroutines.flow.launchIn
1423
import kotlinx.coroutines.flow.map
24+
import kotlinx.coroutines.flow.mapNotNull
1525
import kotlinx.coroutines.flow.onEach
26+
import kotlinx.coroutines.launch
1627
import javax.inject.Inject
1728

1829
@HiltViewModel
1930
internal class BalanceViewModel @Inject constructor(
2031
balanceController: BalanceController,
2132
activityFeedController: ActivityFeedController,
22-
): BaseViewModel2<BalanceViewModel.State, BalanceViewModel.Event>(
33+
transactionController: TransactionController,
34+
userManager: UserManager,
35+
resources: ResourceHelper,
36+
) : BaseViewModel2<BalanceViewModel.State, BalanceViewModel.Event>(
2337
initialState = State(),
2438
updateStateForEvent = updateStateForEvent
2539
) {
@@ -29,9 +43,11 @@ internal class BalanceViewModel @Inject constructor(
2943
)
3044

3145
sealed interface Event {
32-
data class OnBalanceUpdated(val balance: LocalFiat): Event
33-
data object UpdateFeed: Event
34-
data class OnMessagesUpdated(val latest: List<ActivityFeedMessage>): Event
46+
data class OnBalanceUpdated(val balance: LocalFiat) : Event
47+
data object UpdateFeed : Event
48+
data class OnMessagesUpdated(val latest: List<ActivityFeedMessage>) : Event
49+
data class OnCancelRequested(val message: ActivityFeedMessage) : Event
50+
data class CancelTransfer(val vault: PublicKey) : Event
3551
}
3652

3753
init {
@@ -50,12 +66,59 @@ internal class BalanceViewModel @Inject constructor(
5066
dispatchEvent(Event.OnMessagesUpdated(it))
5167
}
5268
).launchIn(viewModelScope)
69+
70+
eventFlow
71+
.filterIsInstance<Event.OnCancelRequested>()
72+
.map { it.message }
73+
.onEach { message ->
74+
val metadata = message.metadata as? FeedMessageMetadata.SentUsdc ?: return@onEach
75+
val formattedAmount = message.amount?.formatted
76+
val title = formattedAmount?.let {
77+
resources.getString(R.string.prompt_title_cancelTransferWithAmount, it)
78+
} ?: resources.getString(R.string.prompt_title_cancelTransferNoAmount)
79+
BottomBarManager.showMessage(
80+
BottomBarManager.BottomBarMessage(
81+
title = title,
82+
subtitle = resources.getString(R.string.prompt_description_cancelTransfer),
83+
positiveText = resources.getString(R.string.action_cancelTransfer),
84+
onPositive = { dispatchEvent(Event.CancelTransfer(vault = metadata.creator)) },
85+
negativeText = resources.getString(R.string.action_nevermind)
86+
)
87+
)
88+
}.launchIn(viewModelScope)
89+
90+
eventFlow
91+
.filterIsInstance<Event.CancelTransfer>()
92+
.map { it.vault }
93+
.mapNotNull { vault ->
94+
val owner = userManager.accountCluster ?: return@mapNotNull null
95+
transactionController.cancelRemoteSend(
96+
vault = vault,
97+
owner = owner,
98+
)
99+
}.onResult(
100+
onError = {
101+
TopBarManager.showMessage(
102+
title = resources.getString(R.string.error_title_failedToCancelTransfer),
103+
message = resources.getString(R.string.error_description_failedToCancelTransfer),
104+
)
105+
},
106+
onSuccess = {
107+
viewModelScope.launch {
108+
activityFeedController.refreshAfterEvent(
109+
type = ActivityFeedType.TransactionHistory
110+
)
111+
}
112+
}
113+
).launchIn(viewModelScope)
53114
}
54115

55116
internal companion object {
56117
val updateStateForEvent: (Event) -> ((State) -> State) = { event ->
57118
when (event) {
58119
Event.UpdateFeed -> { state -> state }
120+
is Event.OnCancelRequested -> { state -> state }
121+
is Event.CancelTransfer -> { state -> state }
59122
is Event.OnBalanceUpdated -> { state -> state.copy(balance = event.balance) }
60123
is Event.OnMessagesUpdated -> { state -> state.copy(feed = event.latest) }
61124
}

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/FeedItem.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import androidx.compose.foundation.layout.Row
77
import androidx.compose.foundation.layout.padding
88
import androidx.compose.material.Text
99
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.derivedStateOf
11+
import androidx.compose.runtime.getValue
12+
import androidx.compose.runtime.remember
1013
import androidx.compose.ui.Alignment
1114
import androidx.compose.ui.Modifier
1215
import com.flipcash.app.core.ui.FlagWithFiat
1316
import com.flipcash.services.models.ActivityFeedMessage
17+
import com.flipcash.services.models.FeedMessageMetadata
1418
import com.getcode.opencode.model.financial.CurrencyCode
1519
import com.getcode.theme.CodeTheme
1620
import com.getcode.util.DateUtils
@@ -21,10 +25,19 @@ import kotlinx.datetime.Instant
2125
internal fun FeedItem(
2226
message: ActivityFeedMessage,
2327
modifier: Modifier = Modifier,
28+
onCancelRequested: () -> Unit,
2429
) {
30+
val canCancel by remember(message.metadata) {
31+
derivedStateOf {
32+
message.metadata ?: return@derivedStateOf false
33+
val metadata = (message.metadata as? FeedMessageMetadata.SentUsdc) ?: return@derivedStateOf false
34+
metadata.canCancel
35+
}
36+
}
37+
2538
Row(
2639
modifier = modifier
27-
.clickable(enabled = false) { } // enable for cancellable items
40+
.clickable(enabled = canCancel) { onCancelRequested() }
2841
.padding(
2942
horizontal = CodeTheme.dimens.inset,
3043
vertical = CodeTheme.dimens.inset,

0 commit comments

Comments
 (0)