Skip to content

Commit 07f29f3

Browse files
committed
fix(flipcash): implement a more refined toast queue and utilize child scopes for streams
Prevent the existing showIfNeeded toast management system from cancelling different bill states Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e8b157f commit 07f29f3

5 files changed

Lines changed: 106 additions & 109 deletions

File tree

apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/bills/BillContainerView.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.flipcash.app.scanner.internal.bills
22

33
import android.Manifest
4-
import android.app.Activity
54
import androidx.activity.compose.BackHandler
65
import androidx.compose.animation.AnimatedVisibility
76
import androidx.compose.animation.EnterExitState
@@ -52,6 +51,7 @@ import com.getcode.ui.utils.AnimationUtils
5251
import com.getcode.util.permissions.PermissionResult
5352
import com.getcode.util.permissions.getPermissionLauncher
5453
import com.getcode.util.permissions.rememberPermissionHandler
54+
import com.getcode.utils.base58
5555
import kotlinx.coroutines.delay
5656

5757
@OptIn(ExperimentalMaterialApi::class)
@@ -67,7 +67,7 @@ internal fun BillContainer(
6767
) {
6868
val session = LocalSessionController.currentOrThrow
6969

70-
val context = LocalContext.current as Activity
70+
val context = LocalContext.current
7171
val onPermissionResult = { result: PermissionResult ->
7272
session.onCameraPermissionResult(result)
7373
if (result == PermissionResult.ShouldShowRationale) {
@@ -137,7 +137,7 @@ internal fun BillContainer(
137137
val updatedState by rememberUpdatedState(state)
138138
val updatedBillState by rememberUpdatedState(billState)
139139

140-
var dismissed by remember {
140+
var dismissed by remember(updatedBillState.bill) {
141141
mutableStateOf(false)
142142
}
143143

@@ -149,7 +149,7 @@ internal fun BillContainer(
149149
val canDismiss =
150150
it == DismissValue.DismissedToEnd && updatedBillState.canSwipeToDismiss
151151
if (canDismiss) {
152-
session.cancelSend()
152+
session.dismissBill()
153153
dismissed = true
154154
}
155155
canDismiss
@@ -240,7 +240,7 @@ internal fun BillContainer(
240240
}
241241

242242
BackHandler(canCancel) {
243-
session.cancelSend()
243+
session.dismissBill()
244244
}
245245
}
246246

@@ -257,7 +257,7 @@ internal fun BillContainer(
257257
) {
258258
ReceivedFundsConfirmation(
259259
bill = updatedBillState.bill as Bill.Cash,
260-
onClaim = { session.cancelSend() }
260+
onClaim = { session.dismissBill() }
261261
)
262262
}
263263
}

apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/SessionController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface SessionController {
2525
fun onCameraScanning(scanning: Boolean)
2626
fun onCameraPermissionResult(result: PermissionResult)
2727
fun showBill(bill: Bill, vibrate: Boolean = false)
28-
fun cancelSend(style: PresentationStyle = PresentationStyle.Slide, overrideToast: Boolean = false)
28+
fun dismissBill(style: PresentationStyle = PresentationStyle.Slide)
2929
fun onCodeScan(code: ScannableKikCode)
3030
fun openCashLink(cashLink: String?)
3131
}

apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ class RealSessionController @Inject constructor(
174174
stopPolling()
175175
billingClient.disconnect()
176176

177+
toastController.clear()
178+
177179
val bill = billController.state.value.bill
178-
//
179180
if (!shareSheetController.isCheckingForShare || (bill != null && !bill.didReceive)) {
180181
BottomBarManager.clear()
181182
billController.cancelAwaitForGrab()
182-
cancelSend()
183+
dismissBill()
183184
}
184185
}
185186

@@ -213,7 +214,7 @@ class RealSessionController @Inject constructor(
213214
type = AirdropType.WelcomeBonus,
214215
destination = it.authority.keyPair
215216
).onSuccess { amount ->
216-
toastController.show(
217+
toastController.enqueue(
217218
amount = amount,
218219
isDeposit = true,
219220
initialDelay = AIRDROP_INITIAL_DELAY
@@ -298,7 +299,7 @@ class RealSessionController @Inject constructor(
298299
),
299300
// Allow cancelling pending outgoing cash bills
300301
secondaryAction = BillState.Action.Cancel(
301-
action = { cancelSend() }
302+
action = { dismissBill() }
302303
),
303304
)
304305
}
@@ -315,19 +316,20 @@ class RealSessionController @Inject constructor(
315316
amount = bill.amount,
316317
owner = owner,
317318
onGrabbed = {
318-
cancelSend(PresentationStyle.Pop)
319+
toastController.enqueue(bill.amount, isDeposit = false)
320+
dismissBill(PresentationStyle.Pop)
319321
vibrator.vibrate()
320322
bringActivityFeedCurrent()
321323
},
322324
onTimeout = {
323-
cancelSend(style = PresentationStyle.Slide)
325+
dismissBill(style = PresentationStyle.Slide)
324326
// analytics.billTimeoutReached(
325327
// bill.amount.kin,
326328
// bill.amount.rate.currency,
327329
// CodeAnalyticsManager.BillPresentationStyle.Slide
328330
// )
329331
},
330-
onError = { cancelSend(style = PresentationStyle.Slide) },
332+
onError = { dismissBill(style = PresentationStyle.Slide) },
331333
present = { data ->
332334
if (!bill.didReceive) {
333335
trace(
@@ -388,9 +390,9 @@ class RealSessionController @Inject constructor(
388390
is ShareConfirmationResult.Confirmed -> {
389391
when (result) {
390392
ShareResult.CopiedToClipboard -> {
391-
cancelSend(PresentationStyle.Pop)
393+
toastController.enqueue(amount)
394+
dismissBill(PresentationStyle.Pop)
392395
vibrator.vibrate()
393-
toastController.show(amount)
394396
bringActivityFeedCurrent()
395397
trace(
396398
tag = "Session",
@@ -403,9 +405,9 @@ class RealSessionController @Inject constructor(
403405
}
404406

405407
is ShareResult.SharedToApp -> {
406-
cancelSend(PresentationStyle.Pop)
408+
toastController.enqueue(amount)
409+
dismissBill(PresentationStyle.Pop)
407410
vibrator.vibrate()
408-
toastController.show(amount)
409411
bringActivityFeedCurrent()
410412
trace(
411413
tag = "Session",
@@ -445,7 +447,7 @@ class RealSessionController @Inject constructor(
445447
cont.resume(Result.success(it))
446448
},
447449
onError = {
448-
cancelSend()
450+
dismissBill()
449451
TopBarManager.showMessage(
450452
title = resources.getString(R.string.error_title_failedToCreateGiftCard),
451453
message = resources.getString(R.string.error_description_failedToCreateGiftCard)
@@ -463,11 +465,11 @@ class RealSessionController @Inject constructor(
463465
vault = giftCard.cluster.vaultPublicKey,
464466
owner = owner,
465467
).onFailure {
466-
cancelSend()
468+
dismissBill()
467469
}.onSuccess {
468470
balanceController.fetchBalance()
469471
checkPendingItemsInFeed()
470-
cancelSend()
472+
dismissBill()
471473
}
472474
}
473475

@@ -570,6 +572,7 @@ class RealSessionController @Inject constructor(
570572
payload = payload,
571573
onGrabbed = { amount ->
572574
BottomBarManager.clear()
575+
toastController.enqueue(amount, isDeposit = true)
573576
showBill(
574577
bill = Bill.Cash(amount = amount, didReceive = true),
575578
vibrate = true
@@ -628,19 +631,12 @@ class RealSessionController @Inject constructor(
628631
}
629632

630633

631-
override fun cancelSend(style: PresentationStyle, overrideToast: Boolean) {
634+
override fun dismissBill(style: PresentationStyle) {
632635
scope.launch {
633-
val shown = toastController.showIfNeeded(style, overrideToast)
634-
_state.update { it.copy(presentationStyle = style) }
635-
billController.reset(showToast = shown)
636-
637-
if (shown) {
638-
delay(ToastController.SHOW_DELAY)
639-
}
640-
if (!overrideToast) {
641-
shareSheetController.reset()
642-
}
643636
billController.reset()
637+
toastController.consumeQueue()
638+
_state.update { it.copy(presentationStyle = style) }
639+
shareSheetController.reset()
644640
}
645641
}
646642
}

apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/toast/ToastController.kt

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.flipcash.app.session.internal.toast
22

33
import com.flipcash.app.core.bill.BillToast
44
import com.flipcash.app.core.internal.bill.BillController
5-
import com.flipcash.app.session.PresentationStyle
65
import com.getcode.opencode.model.financial.LocalFiat
76
import kotlinx.coroutines.CoroutineScope
87
import kotlinx.coroutines.Dispatchers
@@ -21,36 +20,22 @@ class ToastController @Inject constructor(
2120
) {
2221
companion object {
2322
val INITIAL_DELAY = 500.milliseconds
24-
val SHOW_DELAY = 5.seconds
23+
val SHOW_DELAY = 3.seconds
2524
}
2625
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
26+
private val toastQueue = mutableListOf<ToastRequest>()
27+
private var isConsumingQueue = false
2728

28-
fun showIfNeeded(
29-
style: PresentationStyle,
30-
override: Boolean = false
31-
): Boolean {
32-
val billState = billController.state.value
33-
val bill = billState.bill ?: return false
29+
val hasToasts: Boolean
30+
get() = toastQueue.isNotEmpty()
3431

35-
if (!override) {
36-
if (style is PresentationStyle.Pop || billState.showToast) {
37-
show(
38-
amount = bill.metadata.amount,
39-
isDeposit = when (style) {
40-
PresentationStyle.Slide -> true
41-
PresentationStyle.Pop -> false
42-
else -> false
43-
},
44-
)
45-
46-
return true
47-
}
48-
}
49-
50-
return false
51-
}
32+
private data class ToastRequest(
33+
val amount: LocalFiat,
34+
val isDeposit: Boolean,
35+
val initialDelay: Duration = INITIAL_DELAY
36+
)
5237

53-
fun show(
38+
fun enqueue(
5439
amount: LocalFiat,
5540
isDeposit: Boolean = false,
5641
initialDelay: Duration = INITIAL_DELAY
@@ -59,28 +44,67 @@ class ToastController @Inject constructor(
5944
return
6045
}
6146

62-
scope.launch {
63-
delay(initialDelay)
47+
synchronized(toastQueue) {
48+
// Check for matching toast (same amount, opposite isDeposit)
49+
val matchingToast = toastQueue.find { toast ->
50+
toast.amount.converted.doubleValue == amount.converted.doubleValue &&
51+
toast.isDeposit != isDeposit
52+
}
53+
54+
if (matchingToast != null) {
55+
// Cancel matching toast
56+
toastQueue.remove(matchingToast)
57+
} else {
58+
// Enqueue new toast
59+
toastQueue.add(ToastRequest(amount, isDeposit, initialDelay))
60+
}
61+
}
62+
}
63+
64+
fun clear() {
65+
synchronized(toastQueue) {
66+
toastQueue.clear()
67+
if (isConsumingQueue) {
68+
scope.launch {
69+
billController.update {
70+
it.copy(showToast = false, toast = null)
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
suspend fun consumeQueue() {
78+
isConsumingQueue = true
79+
while (hasToasts) {
80+
val toast = synchronized(toastQueue) { toastQueue.removeFirstOrNull() } ?: continue
81+
82+
// Wait for bill animation to finish
83+
// before showing the toast
84+
delay(toast.initialDelay)
6485
billController.update {
6586
it.copy(
6687
showToast = true,
67-
toast = BillToast(amount = amount.converted, isDeposit = isDeposit)
88+
toast = BillToast(amount = toast.amount.converted, isDeposit = toast.isDeposit)
6889
)
6990
}
7091

92+
// Wait for display duration
7193
delay(SHOW_DELAY)
72-
7394
billController.update {
74-
it.copy(
75-
showToast = false
76-
)
95+
it.copy(showToast = false)
7796
}
7897

79-
// wait for animation to run
98+
// Wait for animation to complete
8099
delay(INITIAL_DELAY)
81100
billController.update {
82101
it.copy(toast = null)
83102
}
103+
104+
if (hasToasts) {
105+
delay(1.seconds)
106+
}
84107
}
108+
isConsumingQueue = false
85109
}
86110
}

0 commit comments

Comments
 (0)