Skip to content

Commit feba193

Browse files
authored
fix(amount-entry): reset entered amount when the selected token changes (#1144)
Switching tokens re-denominates an amount entry, so an amount typed in one token was being silently reinterpreted in another. Mirror the existing region/currency reset behaviour: - AmountEntryDelegate gains an optional `tokenChanges` trigger; its init now resets the keypad on either a preferred-rate change or a token change. Wired into the give-cash and chat amount entries (global selected token). - TippingCoordinator clears the tip modal's selected amount on token change. Pinned-token flows (withdrawal, swap) and the picker-less tip keypad screen are intentionally left untouched.
1 parent b5ff96d commit feba193

4 files changed

Lines changed: 35 additions & 8 deletions

File tree

‎apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ internal class CashScreenViewModel @Inject constructor(
8787
loadingState = stateFlow.map { it.generatingBill }
8888
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), LoadingSuccessState()),
8989
maxAmount = maxForGiveFlow,
90+
tokenChanges = tokenCoordinator.observeSelectedTokenMint(),
9091
)
9192
private val tokenInitialized = CompletableDeferred<Mint?>()
9293

‎apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/ChatViewModel.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ internal class ChatViewModel @Inject constructor(
281281
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), LoadingSuccessState()),
282282
maxAmount = maxAmountFlow,
283283
minimumAmount = minAmountFlow,
284+
tokenChanges = tokenCoordinator.observeSelectedTokenMint(),
284285
)
285286
}
286287

‎apps/flipcash/shared/amount-entry/src/main/kotlin/com/flipcash/shared/amountentry/AmountEntryDelegate.kt‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ import com.getcode.ui.components.text.AmountAnimatedInputUiModel
88
import com.getcode.ui.components.text.NumberInputHelper
99
import com.getcode.view.LoadingSuccessState
1010
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.flow.Flow
1112
import kotlinx.coroutines.flow.MutableStateFlow
1213
import kotlinx.coroutines.flow.SharingStarted
1314
import kotlinx.coroutines.flow.StateFlow
1415
import kotlinx.coroutines.flow.asStateFlow
1516
import kotlinx.coroutines.flow.combine
17+
import kotlinx.coroutines.flow.distinctUntilChanged
18+
import kotlinx.coroutines.flow.drop
19+
import kotlinx.coroutines.flow.emptyFlow
1620
import kotlinx.coroutines.flow.filterNotNull
1721
import kotlinx.coroutines.flow.launchIn
22+
import kotlinx.coroutines.flow.merge
1823
import kotlinx.coroutines.flow.onEach
1924
import kotlinx.coroutines.flow.scan
2025
import kotlinx.coroutines.flow.stateIn
@@ -29,6 +34,10 @@ class AmountEntryDelegate(
2934
loadingState: StateFlow<LoadingSuccessState> = MutableStateFlow(LoadingSuccessState()),
3035
maxAmount: StateFlow<Fiat?> = MutableStateFlow(null),
3136
minimumAmount: StateFlow<Fiat?> = MutableStateFlow(null),
37+
// Emits whenever the selected token changes. Like a region/currency change, switching
38+
// tokens re-denominates the entry, so the typed amount is reset (see init). Defaults to a
39+
// no-op for flows without a token concept.
40+
tokenChanges: Flow<*> = emptyFlow<Any?>(),
3241
) : AmountEntryController {
3342
constructor(
3443
exchange: Exchange,
@@ -38,7 +47,8 @@ class AmountEntryDelegate(
3847
loadingState: StateFlow<LoadingSuccessState> = MutableStateFlow(LoadingSuccessState()),
3948
maxAmount: StateFlow<Fiat?> = MutableStateFlow(null),
4049
minimumAmount: StateFlow<Fiat?> = MutableStateFlow(null),
41-
) : this(exchange, scope, maxLength, MutableStateFlow(style), loadingState, maxAmount, minimumAmount)
50+
tokenChanges: Flow<*> = emptyFlow<Any?>(),
51+
) : this(exchange, scope, maxLength, MutableStateFlow(style), loadingState, maxAmount, minimumAmount, tokenChanges)
4252

4353
data class State(
4454
val currency: CurrencyHolder = CurrencyHolder(),
@@ -106,13 +116,13 @@ class AmountEntryDelegate(
106116
init {
107117
numberInputHelper.reset()
108118

109-
exchange.observePreferredRate()
110-
.onEach {
111-
numberInputHelper.reset()
112-
_state.update { s ->
113-
s.copy(amountAnimatedModel = AmountAnimatedInputUiModel())
114-
}
115-
}.launchIn(scope)
119+
// Reset the typed amount whenever the entry is re-denominated: a preferred
120+
// currency/region change (rate) or a selected-token change. `drop(1)` on the token
121+
// stream skips its initial value so an in-flight prefill isn't wiped on construction.
122+
merge(
123+
exchange.observePreferredRate(),
124+
tokenChanges.distinctUntilChanged().drop(1),
125+
).onEach { reset() }.launchIn(scope)
116126
}
117127

118128
fun onCurrencyChanged(currency: Currency) {

‎apps/flipcash/shared/tipping/src/main/kotlin/com/flipcash/shared/tipping/TippingCoordinator.kt‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
3939
import kotlinx.coroutines.flow.SharingStarted
4040
import kotlinx.coroutines.flow.StateFlow
4141
import kotlinx.coroutines.flow.combine
42+
import kotlinx.coroutines.flow.distinctUntilChanged
43+
import kotlinx.coroutines.flow.drop
4244
import kotlinx.coroutines.flow.onStart
4345
import kotlinx.coroutines.flow.firstOrNull
4446
import kotlinx.coroutines.flow.flatMapLatest
47+
import kotlinx.coroutines.flow.launchIn
4548
import kotlinx.coroutines.flow.map
49+
import kotlinx.coroutines.flow.onEach
4650
import kotlinx.coroutines.flow.stateIn
4751
import kotlinx.coroutines.launch
4852
import javax.inject.Inject
@@ -113,6 +117,17 @@ class TippingCoordinator @Inject constructor(
113117
) { state, presets -> state.copy(presets = presets) }
114118
.stateIn(scope, SharingStarted.WhileSubscribed(5_000), TipSelectionState())
115119

120+
init {
121+
// Switching the tip token re-denominates the tip, so clear any amount chosen in the modal
122+
// (preset or custom) — mirroring how the amount-entry keypad resets on a token/region
123+
// change. `drop(1)` skips the initial token resolution so startup doesn't clear anything.
124+
tokenCoordinator.observeSelectedTokenMint()
125+
.distinctUntilChanged()
126+
.drop(1)
127+
.onEach { selectAmount(null) }
128+
.launchIn(scope)
129+
}
130+
116131
/** The largest tippable amount (send-limit ∧ balance), surfaced by the amount entry. */
117132
val maxTipAmount: StateFlow<Fiat?> get() = tipPaymentDelegate.maxTipAmount
118133

0 commit comments

Comments
 (0)