Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ internal class CashScreenViewModel @Inject constructor(
loadingState = stateFlow.map { it.generatingBill }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), LoadingSuccessState()),
maxAmount = maxForGiveFlow,
tokenChanges = tokenCoordinator.observeSelectedTokenMint(),
)
private val tokenInitialized = CompletableDeferred<Mint?>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ internal class ChatViewModel @Inject constructor(
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), LoadingSuccessState()),
maxAmount = maxAmountFlow,
minimumAmount = minAmountFlow,
tokenChanges = tokenCoordinator.observeSelectedTokenMint(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import com.getcode.ui.components.text.AmountAnimatedInputUiModel
import com.getcode.ui.components.text.NumberInputHelper
import com.getcode.view.LoadingSuccessState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
Expand All @@ -29,6 +34,10 @@ class AmountEntryDelegate(
loadingState: StateFlow<LoadingSuccessState> = MutableStateFlow(LoadingSuccessState()),
maxAmount: StateFlow<Fiat?> = MutableStateFlow(null),
minimumAmount: StateFlow<Fiat?> = MutableStateFlow(null),
// Emits whenever the selected token changes. Like a region/currency change, switching
// tokens re-denominates the entry, so the typed amount is reset (see init). Defaults to a
// no-op for flows without a token concept.
tokenChanges: Flow<*> = emptyFlow<Any?>(),
) : AmountEntryController {
constructor(
exchange: Exchange,
Expand All @@ -38,7 +47,8 @@ class AmountEntryDelegate(
loadingState: StateFlow<LoadingSuccessState> = MutableStateFlow(LoadingSuccessState()),
maxAmount: StateFlow<Fiat?> = MutableStateFlow(null),
minimumAmount: StateFlow<Fiat?> = MutableStateFlow(null),
) : this(exchange, scope, maxLength, MutableStateFlow(style), loadingState, maxAmount, minimumAmount)
tokenChanges: Flow<*> = emptyFlow<Any?>(),
) : this(exchange, scope, maxLength, MutableStateFlow(style), loadingState, maxAmount, minimumAmount, tokenChanges)

data class State(
val currency: CurrencyHolder = CurrencyHolder(),
Expand Down Expand Up @@ -106,13 +116,13 @@ class AmountEntryDelegate(
init {
numberInputHelper.reset()

exchange.observePreferredRate()
.onEach {
numberInputHelper.reset()
_state.update { s ->
s.copy(amountAnimatedModel = AmountAnimatedInputUiModel())
}
}.launchIn(scope)
// Reset the typed amount whenever the entry is re-denominated: a preferred
// currency/region change (rate) or a selected-token change. `drop(1)` on the token
// stream skips its initial value so an in-flight prefill isn't wiped on construction.
merge(
exchange.observePreferredRate(),
tokenChanges.distinctUntilChanged().drop(1),
).onEach { reset() }.launchIn(scope)
}

fun onCurrencyChanged(currency: Currency) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand Down Expand Up @@ -113,6 +117,17 @@ class TippingCoordinator @Inject constructor(
) { state, presets -> state.copy(presets = presets) }
.stateIn(scope, SharingStarted.WhileSubscribed(5_000), TipSelectionState())

init {
// Switching the tip token re-denominates the tip, so clear any amount chosen in the modal
// (preset or custom) — mirroring how the amount-entry keypad resets on a token/region
// change. `drop(1)` skips the initial token resolution so startup doesn't clear anything.
tokenCoordinator.observeSelectedTokenMint()
.distinctUntilChanged()
.drop(1)
.onEach { selectAmount(null) }
.launchIn(scope)
}

/** The largest tippable amount (send-limit ∧ balance), surfaced by the amount entry. */
val maxTipAmount: StateFlow<Fiat?> get() = tipPaymentDelegate.maxTipAmount

Expand Down
Loading