From c118a9efc967f8295725f18d21035f1ff5674a8d Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 22 May 2026 09:26:36 -0400 Subject: [PATCH] feat: unify Exchange rate API around preferredRate Replace entryRate with preferredRate across the Exchange interface and all callers. Simplify OpenCodeExchange by removing redundant rate properties and consolidating currency selection logic in PreferredCurrencyController. Signed-off-by: Brandon McAnsh --- .../ui/navigation/AppScreenContent.kt | 2 +- .../kotlin/com/flipcash/app/core/AppRoute.kt | 3 +- .../app/core/money/RegionSelectionKind.kt | 9 --- .../com/flipcash/app/balance/BalanceScreen.kt | 7 +- .../app/cash/internal/CashScreenContent.kt | 7 +- .../app/cash/internal/CashScreenViewModel.kt | 11 +-- .../cash/internal/CashScreenViewModelTest.kt | 4 +- .../flipcash/app/deposit/DepositFlowScreen.kt | 1 - .../tokens/internal/SwapEntryScreenContent.kt | 7 +- .../app/tokens/internal/TokenInfoScreen.kt | 3 +- .../app/withdrawal/WithdrawalFlowScreen.kt | 1 - .../app/withdrawal/WithdrawalViewModel.kt | 11 +-- .../internal/components/TransactionReceipt.kt | 4 +- .../internal/screens/WithdrawalEntryScreen.kt | 5 +- .../currency/PreferredCurrencyController.kt | 66 +++++----------- .../app/currency/RegionSelectionScreen.kt | 8 +- .../currency/internal/CurrencyViewModel.kt | 12 +-- .../InternalPurchaseMethodController.kt | 2 +- .../flipcash/app/tokens/TokenCoordinator.kt | 2 +- .../app/tokens/ui/SelectTokenViewModel.kt | 4 +- .../flipcash/app/tokens/ui/SwapViewModel.kt | 17 ++--- .../app/tokens/ui/TokenInfoViewModel.kt | 4 +- .../controllers/SettingsController.kt | 2 +- .../com/getcode/opencode/compose/Exchange.kt | 21 +---- .../getcode/opencode/compose/ExchangeStub.kt | 15 +--- .../com/getcode/opencode/exchange/Exchange.kt | 11 +-- .../internal/exchange/OpenCodeExchange.kt | 76 +++++-------------- 27 files changed, 81 insertions(+), 234 deletions(-) delete mode 100644 apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/money/RegionSelectionKind.kt diff --git a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt index 652e54a24..c0a1d2a11 100644 --- a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt +++ b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt @@ -90,7 +90,7 @@ fun appEntryProvider( } annotatedEntry { key -> AppRestrictedScreen(key.restrictionType) } annotatedEntry { ScannerScreen() } - annotatedEntry { key -> RegionSelectionScreen(key.kind) } + annotatedEntry { RegionSelectionScreen() } // Sheets (inner content — wrapped in Main.Sheet by navigateTo()) annotatedEntry { key -> CashScreen(key.mint, key.fromTokenInfo) } diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt index 5b93dabd7..56800e490 100644 --- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt +++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt @@ -4,7 +4,6 @@ import android.os.Parcelable import androidx.navigation3.runtime.NavKey import com.flipcash.app.core.deposit.DepositResult import com.flipcash.app.core.deposit.DepositStep -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.tokens.CurrencyCreatorResult import com.flipcash.app.core.tokens.CurrencyCreatorStep import com.flipcash.app.core.tokens.SwapPurpose @@ -71,7 +70,7 @@ sealed interface AppRoute : NavKey, Parcelable { // TODO: is there a better place for this to live? @Serializable - data class RegionSelection(val kind: RegionSelectionKind) : Main + data object RegionSelection : Main @Serializable @Parcelize diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/money/RegionSelectionKind.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/money/RegionSelectionKind.kt deleted file mode 100644 index 0073ae9db..000000000 --- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/money/RegionSelectionKind.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.flipcash.app.core.money - -import kotlinx.serialization.Serializable - -@Serializable -enum class RegionSelectionKind { - Entry, - Balance; -} \ No newline at end of file diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt index 4f8f2b916..25b07dabd 100644 --- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt +++ b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt @@ -11,7 +11,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.flipcash.app.balance.internal.BalanceScreen import com.flipcash.app.balance.internal.BalanceViewModel import com.flipcash.app.core.AppRoute -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.tokens.TokenPurpose import com.flipcash.app.tokens.ui.SelectTokenViewModel import com.flipcash.core.R @@ -55,11 +54,7 @@ fun BalanceScreen() { viewModel.eventFlow .filterIsInstance() .onEach { - navigator.push( - AppRoute.Main.RegionSelection( - RegionSelectionKind.Balance - ) - ) + navigator.push(AppRoute.Main.RegionSelection) }.launchIn(this) } diff --git a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt index ae73f950d..8de8bb958 100644 --- a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt +++ b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.flipcash.app.core.AppRoute -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.ui.AmountWithKeypad import com.flipcash.features.cash.R import com.getcode.navigation.core.LocalCodeNavigator @@ -46,11 +45,7 @@ internal fun GiveScreenContent(viewModel: CashScreenViewModel) { }, isClickable = true, onAmountClicked = { - navigator.push( - AppRoute.Main.RegionSelection( - kind = RegionSelectionKind.Entry - ) - ) + navigator.push(AppRoute.Main.RegionSelection) }, isError = state.isError, onNumberPressed = { viewModel.dispatchEvent(CashScreenViewModel.Event.OnNumberPressed(it)) }, diff --git a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt index 76b50243b..8c7e7b8ba 100644 --- a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt +++ b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt @@ -142,7 +142,7 @@ internal class CashScreenViewModel @Inject constructor( style = BottomBarManager.BottomBarButtonStyle.Filled, ) { viewModelScope.launch { - val rate = exchange.entryRate + val rate = exchange.preferredRate val (token, balance) = stateFlow.value.token!! val amountFiat = verifiedFiatCalculator.compute( amount = Fiat(amount, rate.currency), @@ -212,7 +212,7 @@ internal class CashScreenViewModel @Inject constructor( combine( tokenCoordinator.tokens, tokenCoordinator.balanceForToken(tokenAddress), - exchange.observeEntryRate(), + exchange.observePreferredRate(), ) { tokens, balance, rate -> val token = tokens.find { it.address == tokenAddress } ?: return@combine null TokenWithLocalizedBalance( @@ -232,7 +232,7 @@ internal class CashScreenViewModel @Inject constructor( dispatchEvent(Event.OnCurrencyChanged(it)) }.launchIn(viewModelScope) - exchange.observeEntryRate() + exchange.observePreferredRate() .onEach { // reset when entry rate changes numberInputHelper.reset() @@ -309,7 +309,7 @@ internal class CashScreenViewModel @Inject constructor( .onEach { data -> dispatchEvent(Event.UpdateLoadingState(loading = true)) val (token, balance) = stateFlow.value.token!! - val rate = exchange.entryRate + val rate = exchange.preferredRate val result = verifiedFiatCalculator.compute( amount = Fiat(data.amountData.amount, rate.currency), @@ -352,9 +352,6 @@ internal class CashScreenViewModel @Inject constructor( }.launchIn(viewModelScope) } - override fun onCleared() { - exchange.resetEntryToBalance() - } internal companion object { val updateStateForEvent: (Event) -> ((State) -> State) = { event -> diff --git a/apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt b/apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt index b58852bc6..0a113d95c 100644 --- a/apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt +++ b/apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt @@ -76,8 +76,8 @@ class CashScreenViewModelTest { every { resources.getString(R.string.error_description_sendLimitReached) } returns "error_description_sendLimitReached" // Default stubs for flows consumed in init - every { exchange.observeEntryRate() } returns emptyFlow() - every { exchange.entryRate } returns Rate.oneToOne + every { exchange.observePreferredRate() } returns emptyFlow() + every { exchange.preferredRate } returns Rate.oneToOne every { tokenCoordinator.observeSelectedTokenMint() } returns emptyFlow() every { tokenCoordinator.tokens } returns emptyFlow() every { transactionController.limits } returns MutableStateFlow(null) diff --git a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt index 26249adc8..6f5754bc9 100644 --- a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt +++ b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt @@ -118,7 +118,6 @@ private fun DepositSelectTokenScreen() { modifier = Modifier.fillMaxSize(), tokens = state.tokens, selectedToken = state.selectedToken, - showFlags = true, onTokenSelected = { viewModel.dispatchEvent(SelectTokenViewModel.Event.OnTokenSelected(it.address)) }, ) } diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/SwapEntryScreenContent.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/SwapEntryScreenContent.kt index b1013a923..453c9b19b 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/SwapEntryScreenContent.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/SwapEntryScreenContent.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flipcash.app.core.AppRoute -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.onramp.ui.buildPhantomButtonLabel import com.flipcash.app.core.tokens.FundingSource import com.flipcash.app.core.tokens.SwapPurpose @@ -87,11 +86,7 @@ internal fun SwapEntryScreenContent( decimalPlaces = entryState.currencyModel.fractionUnits, isClickable = (state.purpose as? SwapPurpose.Buy)?.fundingSource != FundingSource.Phantom, onAmountClicked = { - navigator.push( - AppRoute.Main.RegionSelection( - kind = RegionSelectionKind.Entry - ) - ) + navigator.push(AppRoute.Main.RegionSelection) }, isError = state.isError, onNumberPressed = { diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt index e76e83d8a..426d6850f 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt @@ -35,7 +35,6 @@ import com.flipcash.app.analytics.FlipcashAnalyticsService import com.flipcash.app.analytics.rememberAnalytics import com.flipcash.app.core.AppRoute import com.flipcash.app.core.data.Loadable -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.tokens.SwapPurpose import com.flipcash.app.tokens.internal.components.info.MarketCapSection import com.flipcash.app.tokens.internal.components.info.TokenBalance @@ -145,7 +144,7 @@ private fun TokenInfoScreen( onClick = { dispatch( TokenInfoViewModel.Event.OpenScreen( - AppRoute.Main.RegionSelection(kind = RegionSelectionKind.Balance) + AppRoute.Main.RegionSelection ) ) } diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt index dfbfcd8a7..90e69bb2e 100644 --- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt +++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt @@ -123,7 +123,6 @@ private fun WithdrawalSelectTokenScreen() { modifier = Modifier.fillMaxSize(), tokens = state.tokens, selectedToken = state.selectedToken, - showFlags = true, onTokenSelected = { viewModel.dispatchEvent(SelectTokenViewModel.Event.OnTokenSelected(it.address)) }, ) } diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModel.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModel.kt index f21c1a6b2..a97593da8 100644 --- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModel.kt +++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModel.kt @@ -229,7 +229,7 @@ internal class WithdrawalViewModel @Inject constructor( init { numberInputHelper.reset() - dispatchEvent(Event.OnEntryRateUpdated(exchange.entryRate)) + dispatchEvent(Event.OnEntryRateUpdated(exchange.preferredRate)) stateFlow .mapNotNull { it.selectedTokenAddress } @@ -237,7 +237,7 @@ internal class WithdrawalViewModel @Inject constructor( combine( tokenCoordinator.tokens, tokenCoordinator.balanceForToken(tokenAddress), - exchange.observeEntryRate(), + exchange.observePreferredRate(), ) { tokens, balance, rate -> val token = tokens.find { it.address == tokenAddress } ?: return@combine null TokenWithBalance( @@ -271,7 +271,7 @@ internal class WithdrawalViewModel @Inject constructor( numberInputHelper.fractionUnits = it.fractionUnits }.launchIn(viewModelScope) - exchange.observeEntryRate() + exchange.observePreferredRate() .onEach { // reset when entry rate changes numberInputHelper.reset() @@ -329,7 +329,7 @@ internal class WithdrawalViewModel @Inject constructor( .filterNot { checkMinimumExceeded() } .onEach { data -> dispatchEvent(Event.UpdateConfirmingAmountState(loading = true)) - val rate = exchange.entryRate + val rate = exchange.preferredRate val token = stateFlow.value.token!!.token val amountVerified = verifiedFiatCalculator.compute( amount = Fiat(data.amountData.amount, rate.currency), @@ -647,9 +647,6 @@ internal class WithdrawalViewModel @Inject constructor( .launchIn(viewModelScope) } - override fun onCleared() { - exchange.resetEntryToBalance() - } internal companion object { val updateStateForEvent: (Event) -> ((State) -> State) = { event -> diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/components/TransactionReceipt.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/components/TransactionReceipt.kt index 8844df27b..7576af981 100644 --- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/components/TransactionReceipt.kt +++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/components/TransactionReceipt.kt @@ -102,7 +102,7 @@ internal fun TransactionReceipt( } }, formattedBalance = { amount -> - amount.convertingToUsdIfNeeded(exchange.entryRate) + amount.convertingToUsdIfNeeded(exchange.preferredRate) .estimatedTokenAmountIn(tokenWithBalance.token, fractionDigits = 2) }, styling = rememberTokenBalanceRowStyling( @@ -157,7 +157,7 @@ private fun LineItems( label = AnnotatedString( stringResource(R.string.label_amountInToken, tokenWithBalance.displayName) ), - amount = transferAmount.convertingToUsdIfNeeded(exchange.entryRate) + amount = transferAmount.convertingToUsdIfNeeded(exchange.preferredRate) .estimatedTokenAmountIn(tokenWithBalance.token, fractionDigits = 2), ) } diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/WithdrawalEntryScreen.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/WithdrawalEntryScreen.kt index 2610854ab..eedfc0a71 100644 --- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/WithdrawalEntryScreen.kt +++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/WithdrawalEntryScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.flipcash.app.core.AppRoute -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.core.withdrawal.WithdrawalResult import com.flipcash.app.core.withdrawal.WithdrawalStep import com.flipcash.app.withdrawal.WithdrawalViewModel @@ -45,9 +44,7 @@ internal fun WithdrawalEntryScreen( viewModel = viewModel, mint = selectedMint, onOpenRegionSelection = { - codeNavigator.push( - AppRoute.Main.RegionSelection(kind = RegionSelectionKind.Entry) - ) + codeNavigator.push(AppRoute.Main.RegionSelection) }, ) } diff --git a/apps/flipcash/shared/currency-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt b/apps/flipcash/shared/currency-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt index 5b66f71a2..ee88dfa7e 100644 --- a/apps/flipcash/shared/currency-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt +++ b/apps/flipcash/shared/currency-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt @@ -9,7 +9,6 @@ import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.services.controllers.SettingsController import com.flipcash.services.user.UserManager import com.getcode.opencode.exchange.Exchange @@ -43,10 +42,8 @@ class PreferredCurrencyController @Inject constructor( fun initializedKeyForUser(userIdentifier: String) = booleanPreferencesKey("init-$userIdentifier") - fun kindKeyForUser( - kind: RegionSelectionKind, - userIdentifier: String - ) = stringPreferencesKey("${kind.name.lowercase()}-$userIdentifier") + fun currencyKeyForUser(userIdentifier: String) = + stringPreferencesKey("balance-$userIdentifier") fun recentsKeyForUser(userIdentifier: String) = stringSetPreferencesKey("recents-$userIdentifier") @@ -80,34 +77,21 @@ class PreferredCurrencyController @Inject constructor( prefs[initKey] = true } - val balanceCurrency = prefs[kindKeyForUser( - kind = RegionSelectionKind.Balance, - userId - )]?.let { CurrencyCode.tryValueOf(it) } + val currencyCode = prefs[currencyKeyForUser(userId)] + ?.let { CurrencyCode.tryValueOf(it) } ?: CurrencyCode.tryValueOf(locale.getDefaultCurrencyName()) ?: CurrencyCode.USD - // entry defaults to balance — no longer read from DataStore - exchange.setPreferredEntryCurrency(balanceCurrency) - exchange.setPreferredBalanceCurrency(balanceCurrency) + exchange.setPreferredCurrency(currencyCode) } }.launchIn(dataScope) } - fun observePreferredForKind( - kind: RegionSelectionKind - ): Flow { - return when (kind) { - RegionSelectionKind.Entry -> { - exchange.observeEntryRate().map { it.currency.name } - } - RegionSelectionKind.Balance -> { - val identifier = userManager.accountId?.base58 ?: return emptyFlow() - storage.data.map { prefs -> - prefs[kindKeyForUser(kind, identifier)] ?: locale.getDefaultCurrencyName() - } - } + fun observePreferredCurrency(): Flow { + val identifier = userManager.accountId?.base58 ?: return emptyFlow() + return storage.data.map { prefs -> + prefs[currencyKeyForUser(identifier)] ?: locale.getDefaultCurrencyName() } } @@ -117,31 +101,19 @@ class PreferredCurrencyController @Inject constructor( return storage.data.map { prefs -> prefs[recentsKeyForUser(identifier)].orEmpty() } } - suspend fun updateSelection( - kind: RegionSelectionKind, - currency: Currency - ) { + suspend fun updateSelection(currency: Currency) { val code = CurrencyCode.tryValueOf(currency.code) ?: return - when (kind) { - RegionSelectionKind.Entry -> { - // temporary — only update Exchange, don't persist - exchange.setPreferredEntryCurrency(code) - } - RegionSelectionKind.Balance -> { - val identifier = userManager.accountId?.base58 ?: return - storage.edit { prefs -> - prefs[kindKeyForUser(kind, identifier)] = currency.code + val identifier = userManager.accountId?.base58 ?: return + storage.edit { prefs -> + prefs[currencyKeyForUser(identifier)] = currency.code - val recentKey = recentsKeyForUser(identifier) - val recents = prefs[recentKey].orEmpty() + val recentKey = recentsKeyForUser(identifier) + val recents = prefs[recentKey].orEmpty() - prefs[recentKey] = updateRecents(recents, currency.code) - } - exchange.setPreferredBalanceCurrency(code) - exchange.setPreferredEntryCurrency(code) - settingsController.update() - } + prefs[recentKey] = updateRecents(recents, currency.code) } + exchange.setPreferredCurrency(code) + settingsController.update() } suspend fun removeFromRecents( @@ -168,4 +140,4 @@ class PreferredCurrencyController @Inject constructor( return updated } -} \ No newline at end of file +} diff --git a/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/RegionSelectionScreen.kt b/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/RegionSelectionScreen.kt index f1302f2e3..f7232af0a 100644 --- a/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/RegionSelectionScreen.kt +++ b/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/RegionSelectionScreen.kt @@ -3,12 +3,10 @@ package com.flipcash.app.currency import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.currency.internal.CurrencyViewModel import com.flipcash.app.currency.internal.RegionSelectionModalContent import com.flipcash.core.R @@ -16,7 +14,7 @@ import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.ui.components.AppBarWithTitle @Composable -fun RegionSelectionScreen(kind: RegionSelectionKind) { +fun RegionSelectionScreen() { val navigator = LocalCodeNavigator.current Column( modifier = Modifier.fillMaxSize(), @@ -34,9 +32,5 @@ fun RegionSelectionScreen(kind: RegionSelectionKind) { val viewModel = hiltViewModel() RegionSelectionModalContent(viewModel) - - LaunchedEffect(viewModel, kind) { - viewModel.dispatchEvent(CurrencyViewModel.Event.OnKindChanged(kind)) - } } } diff --git a/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/CurrencyViewModel.kt b/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/CurrencyViewModel.kt index b1a09f66d..4ff1d3c40 100644 --- a/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/CurrencyViewModel.kt +++ b/apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/CurrencyViewModel.kt @@ -3,7 +3,6 @@ package com.flipcash.app.currency.internal import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.snapshotFlow import androidx.lifecycle.viewModelScope -import com.flipcash.app.core.money.RegionSelectionKind import com.flipcash.app.currency.PreferredCurrencyController import com.flipcash.features.currency.R import com.getcode.opencode.exchange.Exchange @@ -35,7 +34,6 @@ class CurrencyViewModel @Inject constructor( defaultDispatcher = dispatchers.Default, ) { data class State( - val kind: RegionSelectionKind? = null, val loading: Boolean = false, val listItems: List = emptyList(), val currencies: List = emptyList(), @@ -46,7 +44,6 @@ class CurrencyViewModel @Inject constructor( ) sealed interface Event { - data class OnKindChanged(val kind: RegionSelectionKind): Event data class OnLoadingChanged(val loading: Boolean) : Event data class OnItemsPopulated(val currencies: List) : Event data class OnCurrenciesUpdated(val currencies: List): Event @@ -61,8 +58,7 @@ class CurrencyViewModel @Inject constructor( init { combine( exchange.observeRates().distinctUntilChanged().map { exchange.getCurrenciesWithRates() }, - stateFlow.mapNotNull { it.kind } - .flatMapLatest { preferredCurrencyController.observePreferredForKind(it) }.distinctUntilChanged(), + preferredCurrencyController.observePreferredCurrency().distinctUntilChanged(), ) { currenciesWithRates, preferredCurrency -> dispatchEvent(Event.OnCurrenciesUpdated(currenciesWithRates)) val preferredWithRate = currenciesWithRates.find { it.code == preferredCurrency } @@ -86,8 +82,7 @@ class CurrencyViewModel @Inject constructor( .filter { it.fromUser } .map { it.currency } .onEach { selected -> - val kind = stateFlow.value.kind ?: return@onEach - preferredCurrencyController.updateSelection(kind, selected) + preferredCurrencyController.updateSelection(selected) }.onEach { dispatchEvent(Event.OnSelectedCurrencyChanged) } .launchIn(viewModelScope) @@ -157,7 +152,6 @@ class CurrencyViewModel @Inject constructor( val updateStateForEvent: (Event) -> ((State) -> State) = { event -> when (event) { is Event.OnItemsPopulated -> { state -> state.copy(listItems = event.currencies) } - is Event.OnKindChanged -> { state -> state.copy(kind = event.kind) } is Event.OnLoadingChanged -> { state -> state.copy(loading = event.loading) } is Event.OnCurrenciesUpdated -> { state -> state.copy(currencies = event.currencies) } is Event.OnRecentCurrenciesUpdated -> { state -> state.copy(recents = event.recents) } @@ -174,4 +168,4 @@ class CurrencyViewModel @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt b/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt index cabfe5222..0730e8030 100644 --- a/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt +++ b/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt @@ -67,7 +67,7 @@ class InternalPurchaseMethodController @Inject constructor( combine( reservesBalanceProvider.observeReservesBalance(), - exchange.observeBalanceRate(), + exchange.observePreferredRate(), ) { balance, rate -> LocalFiat( usdf = balance, diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt index 5ed7898ed..ad14c5fe9 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt @@ -514,7 +514,7 @@ class TokenCoordinator @Inject constructor( val resolved = resolveTokenSelection( balances = _state.value.balances, currentSelection = currentSelection, - rate = exchange.entryRate, + rate = exchange.preferredRate, ) if (resolved != null && resolved != currentSelection) { diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt index 2712523ac..a0818b4db 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt @@ -102,10 +102,8 @@ class SelectTokenViewModel @Inject constructor( stateFlow, tokenCoordinator.tokenBalances, when (purpose) { - TokenPurpose.Balance -> exchange.observeBalanceRate() - TokenPurpose.Select -> exchange.observeEntryRate() TokenPurpose.Withdraw -> flowOf(exchange.rateForUsd()) - TokenPurpose.Deposit -> exchange.observeEntryRate() + else -> exchange.observePreferredRate() } ) { state, balances, rate -> dispatchEvent(Event.OnRateChanged(rate)) diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt index 2a8042913..620e062ca 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt @@ -333,7 +333,7 @@ class SwapViewModel @Inject constructor( combine( tokenCoordinator.tokenBalances, - exchange.observeEntryRate(), + exchange.observePreferredRate(), ) { tokens, rate -> var token = tokens.find { it.token.address == mint } if (token == null) { @@ -379,7 +379,7 @@ class SwapViewModel @Inject constructor( combine( tokenCoordinator.tokens, tokenCoordinator.balanceForToken(tokenAddress), - exchange.observeEntryRate(), + exchange.observePreferredRate(), ) { tokens, balance, rate -> val token = tokens.find { it.address == tokenAddress } ?: return@combine null TokenWithLocalizedBalance( @@ -398,7 +398,7 @@ class SwapViewModel @Inject constructor( combine( tokenCoordinator.observeReservesBalance(), - exchange.observeEntryRate(), + exchange.observePreferredRate(), ) { balance, rate -> LocalFiat( usdf = balance, @@ -410,7 +410,7 @@ class SwapViewModel @Inject constructor( dispatchEvent(Event.OnReservesUpdated(TokenWithBalance(Token.usdf, it.nativeAmount))) }.launchIn(viewModelScope) - exchange.observeEntryRate() + exchange.observePreferredRate() .onEach { numberInputHelper.reset() if (stateFlow.value.pendingInitialAmount == null) { @@ -508,7 +508,7 @@ class SwapViewModel @Inject constructor( .onEach { (data, purpose) -> when (purpose) { is SwapPurpose.Buy -> { - val rate = exchange.entryRate + val rate = exchange.preferredRate val conversionRate = exchange.rateToUsd( stateFlow.value.amountEntryState.currencyModel.code ?: CurrencyCode.USD ) ?: Rate.ignore @@ -561,7 +561,7 @@ class SwapViewModel @Inject constructor( } is SwapPurpose.Sell -> { - val rate = exchange.entryRate + val rate = exchange.preferredRate val tokenWithBalance = stateFlow.value.tokenWithBalance!! val amountFiat = verifiedFiatCalculator.compute( amount = Fiat(data.amountData.amount, rate.currency), @@ -806,7 +806,7 @@ class SwapViewModel @Inject constructor( return@onEach } - val rate = exchange.entryRate + val rate = exchange.preferredRate val amountFiat = verifiedFiatCalculator.compute( amount = amount, token = Token.usdf, @@ -1053,9 +1053,6 @@ class SwapViewModel @Inject constructor( dispatchEvent(Event.UpdateBuyState()) } - override fun onCleared() { - exchange.resetEntryToBalance() - } private fun enterAmount(amount: Fiat) { numberInputHelper.maxLength = 10 diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt index 508a30f06..7f2c4daa1 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt @@ -139,7 +139,7 @@ class TokenInfoViewModel @Inject constructor( combine( tokenCoordinator.balanceForToken(token.address), tokenCoordinator.appreciationForToken(token.address), - exchange.observeBalanceRate(), + exchange.observePreferredRate(), ) { balance, appreciation, rate -> val localizedBalance = LocalFiat( usdf = balance, @@ -241,7 +241,7 @@ class TokenInfoViewModel @Inject constructor( .flatMapLatest { mcap -> combine( flowOf(mcap), - exchange.observeBalanceRate(), + exchange.observePreferredRate(), ) { usdMcap, rate -> usdMcap?.convertingTo(rate) } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/controllers/SettingsController.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/controllers/SettingsController.kt index 49c2cef24..be9669431 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/controllers/SettingsController.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/controllers/SettingsController.kt @@ -15,7 +15,7 @@ class SettingsController @Inject constructor( ) { suspend fun update(): Result { val locale = localeHelper.getLanguageTag() - val currencyRegion = exchange.balanceRate.currency + val currencyRegion = exchange.preferredRate.currency val owner = userManager.accountCluster?.authority?.keyPair ?: return Result.failure(Throwable("No account cluster in UserManager")) diff --git a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt b/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt index 183de2398..600cfaf18 100644 --- a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt +++ b/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt @@ -13,29 +13,14 @@ import kotlinx.coroutines.flow.emptyFlow val LocalExchange: ProvidableCompositionLocal = staticCompositionLocalOf { ExchangeNull() } private class ExchangeNull : Exchange { - override val balanceRate: Rate + override val preferredRate: Rate get() = Rate.oneToOne - override val entryRate: Rate - get() = Rate.oneToOne - - - override fun observeEntryRate(): Flow { + override fun observePreferredRate(): Flow { return emptyFlow() } - override suspend fun setPreferredEntryCurrency(currencyCode: CurrencyCode) { - - } - - override fun resetEntryToBalance() = Unit - - override fun observeBalanceRate(): Flow { - return emptyFlow() - } - - override suspend fun setPreferredBalanceCurrency(currencyCode: CurrencyCode) { - + override suspend fun setPreferredCurrency(currencyCode: CurrencyCode) { } override fun updateUserMints(mints: List) = Unit diff --git a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt b/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt index c477a4acb..1d52f0f74 100644 --- a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt +++ b/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt @@ -7,7 +7,6 @@ import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.opencode.model.financial.Rate import com.getcode.services.opencode.R import com.getcode.solana.keys.Mint -import com.getcode.solana.keys.Signature import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -16,21 +15,13 @@ import kotlinx.coroutines.withContext class ExchangeStub( private val providedRates: Map = emptyMap(), - private val providedProofs: Map = emptyMap(), private val context: Context, ): Exchange { - override val entryRate: Rate = Rate.oneToOne + override val preferredRate: Rate = Rate.oneToOne - override fun observeEntryRate(): Flow = emptyFlow() + override fun observePreferredRate(): Flow = emptyFlow() - override suspend fun setPreferredEntryCurrency(currencyCode: CurrencyCode) = Unit - override fun resetEntryToBalance() = Unit - - override val balanceRate: Rate = Rate.oneToOne - - override fun observeBalanceRate(): Flow = emptyFlow() - - override suspend fun setPreferredBalanceCurrency(currencyCode: CurrencyCode) = Unit + override suspend fun setPreferredCurrency(currencyCode: CurrencyCode) = Unit override fun updateUserMints(mints: List) = Unit override fun rates(): Map = providedRates diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/exchange/Exchange.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/exchange/Exchange.kt index 20b6407ef..5b8f0027b 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/exchange/Exchange.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/exchange/Exchange.kt @@ -4,17 +4,12 @@ import com.getcode.opencode.model.financial.Currency import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.opencode.model.financial.Rate import com.getcode.solana.keys.Mint -import com.getcode.solana.keys.Signature import kotlinx.coroutines.flow.Flow interface Exchange { - val entryRate: Rate - fun observeEntryRate(): Flow - suspend fun setPreferredEntryCurrency(currencyCode: CurrencyCode) - fun resetEntryToBalance() - val balanceRate: Rate - fun observeBalanceRate(): Flow - suspend fun setPreferredBalanceCurrency(currencyCode: CurrencyCode) + val preferredRate: Rate + fun observePreferredRate(): Flow + suspend fun setPreferredCurrency(currencyCode: CurrencyCode) fun updateUserMints(mints: List) diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt index 138d0b3e3..256c4f365 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt @@ -43,7 +43,7 @@ internal class OpenCodeExchange @Inject constructor( ProcessLifecycleOwner.get().lifecycle.addObserver(this) scope.launch { val currencyCode = locale.getDefaultCurrencyName() - balanceCurrency = CurrencyCode.tryValueOf(currencyCode) + preferredCurrency = CurrencyCode.tryValueOf(currencyCode) } } @@ -57,20 +57,20 @@ internal class OpenCodeExchange @Inject constructor( } - private val _balanceRate = MutableStateFlow(Rate.oneToOne) - override val balanceRate - get() = _balanceRate.value + private val _preferredRate = MutableStateFlow(Rate.oneToOne) + override val preferredRate: Rate + get() = _preferredRate.value - override fun observeBalanceRate(): Flow = _balanceRate + override fun observePreferredRate(): Flow = _preferredRate private val mints = MutableStateFlow>(emptyList()) - override suspend fun setPreferredBalanceCurrency(currencyCode: CurrencyCode) { - balanceCurrency = currencyCode + override suspend fun setPreferredCurrency(currencyCode: CurrencyCode) { + preferredCurrency = currencyCode verifiedStateManager.rateFor(currencyCode)?.let { - _balanceRate.value = it + _preferredRate.value = it } ?: run { - _balanceRate.value = Rate.oneToOne.copy(currency = currencyCode) + _preferredRate.value = Rate.oneToOne.copy(currency = currencyCode) } } @@ -78,27 +78,7 @@ internal class OpenCodeExchange @Inject constructor( this.mints.value = mints } - private val _entryRate = MutableStateFlow(Rate.oneToOne) - override val entryRate: Rate - get() = _entryRate.value - - override fun observeEntryRate(): Flow = _entryRate - - override suspend fun setPreferredEntryCurrency(currencyCode: CurrencyCode) { - entryCurrency = currencyCode - verifiedStateManager.rateFor(currencyCode)?.let { - _entryRate.value = it - } ?: run { - _entryRate.value = Rate.oneToOne.copy(currency = currencyCode) - } - } - - override fun resetEntryToBalance() { - scope.launch { setPreferredEntryCurrency(balanceRate.currency) } - } - - private var balanceCurrency: CurrencyCode? = null - private var entryCurrency: CurrencyCode? = null + private var preferredCurrency: CurrencyCode? = null override fun rates() = verifiedStateManager.rates() override fun observeRates(): Flow> = verifiedStateManager.observeRates() @@ -175,47 +155,25 @@ internal class OpenCodeExchange @Inject constructor( } private fun updateRates(rates: Map) { - val balanceRate = balanceCurrency?.let { rates[it] } - val balanceChanged = _balanceRate.value != balanceRate - if (balanceChanged) { - _balanceRate.value = if (balanceRate != null) { + val rate = preferredCurrency?.let { rates[it] } + val changed = _preferredRate.value != rate + if (changed) { + _preferredRate.value = if (rate != null) { trace( tag = "Background", - message = "Updated the local currency: $balanceCurrency", + message = "Updated the preferred currency: $preferredCurrency", type = TraceType.Process ) - balanceRate + rate } else { trace( tag = "Background", - message = "local:: Rate for $balanceCurrency not found. Defaulting to USD.", + message = "Rate for $preferredCurrency not found. Defaulting to USD.", type = TraceType.Process ) rates[CurrencyCode.USD] ?: Rate.oneToOne } - } - - val entryRate = entryCurrency?.let { rates[it] } - val entryChanged = _entryRate.value != entryRate - if (entryChanged) { - _entryRate.value = if (entryRate != null) { - trace( - tag = "Background", - message = "Updated the entry currency: $entryCurrency", - type = TraceType.Process - ) - entryRate - } else { - trace( - tag = "Background", - message = "entry:: Rate for $entryCurrency not found. Defaulting to USD.", - type = TraceType.Process - ) - rates[CurrencyCode.USD] ?: Rate.oneToOne - } - } - if (balanceChanged || entryChanged) { trace( tag = "Background", message = "Updated rates",