diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index e44d8366ab..65bbb228d0 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -26,6 +26,7 @@ import com.tangem.domain.DomainLayer import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.LogConfig +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletManagersRepository import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles @@ -41,8 +42,8 @@ import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader -import com.tangem.tap.common.log.TimberFormatStrategy import com.tangem.tap.common.log.TangemLogCollector +import com.tangem.tap.common.log.TimberFormatStrategy import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction @@ -166,6 +167,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var appCurrencyRepository: AppCurrencyRepository + @Inject + lateinit var walletManagersFacade: WalletManagersFacade + override fun onCreate() { super.onCreate() @@ -185,6 +189,7 @@ class TapApplication : Application(), ImageLoaderFactory { tokenDetailsFeatureToggles = tokenDetailsFeatureToggles, scanCardProcessor = scanCardProcessor, appCurrencyRepository = appCurrencyRepository, + walletManagersFacade = walletManagersFacade, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt b/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt index 05cc610f95..5016fe7d2e 100644 --- a/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt +++ b/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt @@ -1,6 +1,7 @@ package com.tangem.tap.di import com.tangem.core.navigation.NavigationStateHolder +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.tap.proxy.AppStateHolder import dagger.Binds @@ -20,4 +21,8 @@ internal interface AppStateHolderModule { @Binds @Singleton fun bindsNavigationStateHolder(appStateHolder: AppStateHolder): NavigationStateHolder + + @Binds + @Singleton + fun bindsReduxStateHolder(appStateHolder: AppStateHolder): ReduxStateHolder } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index fcf584cf31..1de357e9df 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.demo import com.tangem.domain.demo.DemoConfig import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.DetailsAction @@ -21,8 +22,8 @@ object DemoHelper { private val disabledActionFeatures = listOf( WalletConnectAction.StartWalletConnect::class.java, - WalletAction.TradeCryptoAction.Buy::class.java, - WalletAction.TradeCryptoAction.Sell::class.java, + TradeCryptoAction.Buy::class.java, + TradeCryptoAction.Sell::class.java, BackupAction.StartBackup::class.java, WalletAction.ExploreAddress::class.java, DetailsAction.ResetToFactory.Start::class.java, diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt index 9d28a45db6..2879f9ef13 100644 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt @@ -1,9 +1,9 @@ package com.tangem.tap.features.intentHandler.handlers import android.content.Intent +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.features.intentHandler.IntentHandler -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import timber.log.Timber @@ -22,7 +22,7 @@ class SellCurrencyIntentHandler : IntentHandler { Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") store.dispatchWithMain( - WalletAction.TradeCryptoAction.SendCrypto( + TradeCryptoAction.SendCrypto( currencyId = currency, amount = amount, destinationAddress = destinationAddress, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 628e934435..cb2e4c26bd 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -19,6 +19,7 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic @@ -38,7 +39,6 @@ import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers @@ -254,7 +254,7 @@ private fun sendTransaction( ), ) Analytics.sendSelectedCurrencyEvent(mainCurrencyType) - dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) + dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) } else { Analytics.send( Basic.TransactionSent( diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 748ecacb4b..0e963d3ac5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -18,6 +18,7 @@ import com.google.android.material.textfield.TextInputEditText import com.tangem.Message import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.sdk.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.analytics.events.Token @@ -39,7 +40,6 @@ import com.tangem.tap.features.send.redux.FeeActionUi.* import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.mainScope import com.tangem.tap.store @@ -337,9 +337,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { if (externalTransactionData == null) { store.dispatch(NavigationAction.PopBackTo()) } else { - store.dispatch( - WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId), - ) + store.dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 65be537ac8..2528753b4e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -4,12 +4,16 @@ import androidx.core.os.bundleOf import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token @@ -27,6 +31,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch @@ -35,19 +40,27 @@ import kotlinx.serialization.json.Json import com.tangem.feature.swap.domain.models.domain.Currency as SwapCurrency class TradeCryptoMiddleware { - fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { + fun handle(state: () -> AppState?, action: TradeCryptoAction) { if (DemoHelper.tryHandle(state, action)) return when (action) { - is WalletAction.TradeCryptoAction.Buy -> proceedBuyAction(state, action) - is WalletAction.TradeCryptoAction.Sell -> proceedSellAction() - is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) - is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) - is WalletAction.TradeCryptoAction.Swap -> openSwap() + is TradeCryptoAction.Buy -> proceedBuyAction(state, action) + is TradeCryptoAction.Sell -> proceedSellAction() + is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) + is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) + is TradeCryptoAction.Swap -> { + openSwap(currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency()) + } + is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action) + TradeCryptoAction.New.Send -> store.dispatch(WalletAction.Send()) + is TradeCryptoAction.New.Sell -> proceedNewSellAction(action) + is TradeCryptoAction.New.Swap -> { + openSwap(currency = action.cryptoCurrency.toSwapCurrency()) + } } } - private fun proceedBuyAction(state: () -> AppState?, action: WalletAction.TradeCryptoAction.Buy) { + private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { val selectedWalletData = store.state.walletState.selectedWalletData ?: return val currency = chooseAppropriateCurrency(store.state.walletState) ?: return @@ -75,7 +88,7 @@ class TradeCryptoMiddleware { buyErc20TestnetTokens( card = card, walletManager = walletManager, - token = currency.token, + destinationAddress = currency.token.contractAddress, ) } return @@ -93,6 +106,56 @@ class TradeCryptoMiddleware { } } + private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) { + val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return + + if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) { + store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog()) + return + } + + val status = action.cryptoCurrencyStatus + val currency = status.currency + val blockchain = Blockchain.fromId(currency.network.id.value) + if (currency is CryptoCurrency.Token && currency.network.isTestnet) { + scope.launch { + val walletManager = store.state.daggerGraphState + .get(DaggerGraphState::walletManagersFacade) + .getOrCreateWalletManager( + userWallet = action.userWallet, + blockchain = blockchain, + derivationPath = blockchain.derivationPath( + style = action.userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(), + ), + ) + + if (walletManager !is EthereumWalletManager) { + store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum") + return@launch + } + + buyErc20TestnetTokens( + card = action.userWallet.scanResponse.card, + walletManager = walletManager, + destinationAddress = currency.contractAddress, + ) + } + return + } + + val exchangeManager = store.state.globalState.exchangeManager + exchangeManager.getUrl( + action = CurrencyExchangeManager.Action.Buy, + blockchain = blockchain, + cryptoCurrencyName = currency.symbol, + fiatCurrencyName = action.appCurrencyCode, + walletAddress = networkAddress, + )?.let { + store.dispatchOpenUrl(it) + Analytics.send(Token.Topup.ScreenOpened()) + } + } + private fun proceedSellAction() { val selectedWalletData = store.state.walletState.selectedWalletData ?: return val currency = chooseAppropriateCurrency(store.state.walletState) ?: return @@ -115,6 +178,22 @@ class TradeCryptoMiddleware { } } + private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) { + val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return + val currency = action.cryptoCurrencyStatus.currency + + store.state.globalState.exchangeManager.getUrl( + action = CurrencyExchangeManager.Action.Sell, + blockchain = Blockchain.fromId(currency.network.id.value), + cryptoCurrencyName = currency.symbol, + fiatCurrencyName = action.appCurrencyCode, + walletAddress = networkAddress, + )?.let { + store.dispatchOpenUrl(it) + Analytics.send(Token.Withdraw.ScreenOpened()) + } + } + private fun chooseAppropriateCurrency(walletState: WalletState): Currency? { return if (walletState.primaryTokenData == null) { walletState.selectedWalletData?.currency @@ -126,7 +205,7 @@ class TradeCryptoMiddleware { } } - private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { + private fun preconfigureAndOpenSendScreen(action: TradeCryptoAction.SendCrypto) { val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) @@ -160,16 +239,44 @@ class TradeCryptoMiddleware { )?.let { store.dispatchOpenUrl(it) } } - private fun openSwap() { - val currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency() - val bundle = - bundleOf( - SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency), - SwapFragment.DERIVATION_PATH to store.state.walletState.selectedWalletData?.currency?.derivationPath, - ) + private fun openSwap(currency: SwapCurrency?) { + val bundle = bundleOf( + SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency), + SwapFragment.DERIVATION_PATH to store.state.walletState.selectedWalletData?.currency?.derivationPath, + ) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle)) } + private fun CryptoCurrency.toSwapCurrency(): SwapCurrency { + val blockchain = Blockchain.fromId(network.id.value) + + return when (this) { + is CryptoCurrency.Coin -> { + SwapCurrency.NativeToken( + id = blockchain.toCoinId(), + name = name, + symbol = symbol, + networkId = blockchain.toNetworkId(), + // no need to set logoUrl for blockchain cause + // error when form url with coinId, coinId of eth and arbitrum the same + logoUrl = "", + ) + } + is CryptoCurrency.Token -> { + SwapCurrency.NonNativeToken( + id = id.value, + name = name, + symbol = symbol, + networkId = blockchain.toNetworkId(), + logoUrl = getIconUrl(id.value), + contractAddress = contractAddress, + decimalCount = decimals, + ) + } + } + } + private fun Currency.toSwapCurrency(): SwapCurrency { return when (this) { is Currency.Blockchain -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index c84ac7fa2d..a6a5fa0e78 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.userwallets.GetCardImageUseCase import com.tangem.domain.wallets.legacy.lockIfLockable import com.tangem.tap.* @@ -87,7 +88,7 @@ class WalletMiddleware { val walletState = store.state.walletState when (action) { - is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) + is TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState) is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index e91e0a1194..3fb8a17540 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -68,7 +68,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS error = null, ) } - is WalletAction.TradeCryptoAction -> return newState is WalletAction.AppCurrencyAction -> { newState = appCurrencyReducer.reduce(action, newState) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index f515410168..94e15e9e65 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -21,6 +21,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.feature.swap.api.SwapFeatureToggleManager import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.sdk.extensions.dpToPx @@ -276,9 +277,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt ) { val exchangeManager = store.state.globalState.exchangeManager binding.rowButtons.apply { - onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } - onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } - onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) } + onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) } + onSellClick = { store.dispatch(TradeCryptoAction.Sell) } + onSwapClick = { store.dispatch(TradeCryptoAction.Swap) } onTradeClick = { store.dispatch( WalletAction.DialogAction.ChooseTradeActionDialog( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt index 1495219c54..25ce55e44c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt @@ -4,6 +4,7 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.extensions.show import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.models.WalletDialog @@ -39,15 +40,15 @@ class ChooseTradeActionBottomSheetDialog( dialogBtnBuy.setOnClickListener { dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Buy()) + store.dispatch(TradeCryptoAction.Buy()) } dialogBtnSell.setOnClickListener { dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Sell) + store.dispatch(TradeCryptoAction.Sell) } dialogBtnSwap.setOnClickListener { dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Swap) + store.dispatch(TradeCryptoAction.Swap) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index b7ec4ee4a1..185c5e366a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -6,10 +6,10 @@ import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding @@ -40,7 +40,7 @@ class RussianCardholdersWarningBottomSheetDialog( if (dialogData != null) { store.dispatchOpenUrl(dialogData.topUpUrl) } else { - store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false)) + store.dispatch(TradeCryptoAction.Buy(checkUserLocation = false)) } dismiss() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 23a4645bb2..7ce1a4d49e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -4,6 +4,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.core.analytics.Analytics +import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.* import com.tangem.tap.domain.model.WalletDataModel @@ -151,9 +152,9 @@ class SingleWalletView : WalletView() { val exchangeManager = store.state.globalState.exchangeManager binding?.rowButtons?.apply { - onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } - onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } - onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) } + onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) } + onSellClick = { store.dispatch(TradeCryptoAction.Sell) } + onSwapClick = { store.dispatch(TradeCryptoAction.Swap) } onTradeClick = { store.dispatch( WalletAction.DialogAction.ChooseTradeActionDialog( diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index d3294b1ed2..ad03f2c04a 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -5,7 +5,6 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.domain.models.scan.CardDTO @@ -87,17 +86,12 @@ class CurrencyExchangeManager( } } -suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, token: Token) { +suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) { walletManager.safeUpdate() val amountToSend = Amount(walletManager.wallet.blockchain) - val destinationAddress = token.contractAddress - - val feeResult = walletManager.getFee( - amountToSend, - destinationAddress, - ) as? Result.Success ?: return + val feeResult = walletManager.getFee(amountToSend, destinationAddress) as? Result.Success ?: return val fee = when (val feeForTx = feeResult.data) { is TransactionFee.Choosable -> feeForTx.minimum is TransactionFee.Single -> feeForTx.normal @@ -106,8 +100,6 @@ suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletMa val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO if (coinValue < fee.amount.value) return - val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) - val signer = TangemSigner( card = card, tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk, @@ -121,5 +113,13 @@ suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletMa ), ) } - walletManager.send(transaction, signer) + + walletManager.send( + transactionData = walletManager.createTransaction( + amount = amountToSend, + fee = fee, + destination = destinationAddress, + ), + signer = signer, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 8ea80aec87..b89e7cbe68 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -4,6 +4,7 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.NavigationStateHolder import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.tap.common.entities.FiatCurrency @@ -14,6 +15,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.features.wallet.redux.WalletState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import org.rekotlin.Action import org.rekotlin.Store import javax.inject.Inject @@ -21,7 +23,7 @@ import javax.inject.Inject * Holds objects from old modules, that missing in DI graph. * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI. */ -class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationStateHolder { +class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationStateHolder, ReduxStateHolder { override var userWalletsListManager: UserWalletsListManager? = null set(value) { @@ -50,4 +52,8 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationState override fun navigate(action: NavigationAction) { mainStore?.dispatch(action) } + + override fun dispatch(action: Action) { + mainStore?.dispatch(action) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index b9be4b9f93..c677f70300 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -6,6 +6,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -33,6 +34,7 @@ data class DaggerGraphState( val scanCardProcessor: ScanCardProcessor? = null, val cardSdkConfigRepository: CardSdkConfigRepository? = null, val appCurrencyRepository: AppCurrencyRepository? = null, + val walletManagersFacade: WalletManagersFacade? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt new file mode 100644 index 0000000000..4d43b6da00 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.redux + +import org.rekotlin.Action + +interface ReduxStateHolder { + + fun dispatch(action: Action) +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index e119f09792..deb238337d 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -168,7 +168,7 @@ class DefaultWalletManagersFacade( } } - private suspend fun getOrCreateWalletManager( + override suspend fun getOrCreateWalletManager( userWallet: UserWallet, blockchain: Blockchain, derivationPath: DerivationPath?, diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index c923ac2c76..4c296b01e4 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -1,11 +1,15 @@ package com.tangem.domain.walletmanager +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Network import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId // TODO: Move to its own module @@ -60,4 +64,10 @@ interface WalletManagersFacade { page: Int, pageSize: Int, ): PaginationWrapper + + suspend fun getOrCreateWalletManager( + userWallet: UserWallet, + blockchain: Blockchain, + derivationPath: DerivationPath?, + ): WalletManager? } \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 1ca46ccf7a..6fae380259 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { /** Project - Domain */ implementation(projects.domain.core) + implementation(projects.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) implementation(projects.domain.appCurrency.models) @@ -16,6 +17,7 @@ dependencies { /** Utils */ implementation(deps.jodatime) + implementation(deps.reKotlin) /** Tests */ testImplementation(deps.test.junit) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt new file mode 100644 index 0000000000..3ef7c99d04 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt @@ -0,0 +1,43 @@ +package com.tangem.domain.tokens.legacy + +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.models.CryptoCurrency +import com.tangem.domain.wallets.models.UserWallet +import org.rekotlin.Action + +sealed class TradeCryptoAction : Action { + + data class Buy(val checkUserLocation: Boolean = true) : TradeCryptoAction() + + object Sell : TradeCryptoAction() + + data class SendCrypto( + val currencyId: String, + val amount: String, + val destinationAddress: String, + val transactionId: String, + ) : TradeCryptoAction() + + data class FinishSelling(val transactionId: String) : TradeCryptoAction() + + object Swap : TradeCryptoAction() + + sealed class New : TradeCryptoAction() { + + data class Buy( + val userWallet: UserWallet, + val cryptoCurrencyStatus: CryptoCurrencyStatus, + val appCurrencyCode: String, + val checkUserLocation: Boolean = true, + ) : New() + + data class Sell( + val cryptoCurrencyStatus: CryptoCurrencyStatus, + val appCurrencyCode: String, + ) : New() + + object Send : New() + + data class Swap(val cryptoCurrency: CryptoCurrency) : New() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 9c167104ee..b71f6496b5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -40,6 +40,9 @@ data class CryptoCurrencyStatus( /** The pending cryptocurrency transactions. */ open val pendingTransactions: Set = emptySet() + + /** The network address */ + open val networkAddress: NetworkAddress? = null } /** Represents the Loading state of a token, typically while fetching its details. */ @@ -72,6 +75,7 @@ data class CryptoCurrencyStatus( override val priceChange: BigDecimal, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, + override val networkAddress: NetworkAddress?, ) : Status() /** @@ -92,6 +96,7 @@ data class CryptoCurrencyStatus( override val priceChange: BigDecimal?, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, + override val networkAddress: NetworkAddress?, ) : Status() /** @@ -106,5 +111,6 @@ data class CryptoCurrencyStatus( override val amount: BigDecimal, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, + override val networkAddress: NetworkAddress?, ) : Status() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 3498bb05e3..2082010b8d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -35,6 +35,7 @@ internal class CurrencyStatusOperations( amount = amount, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, + networkAddress = status.address, ) currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( amount = amount, @@ -43,6 +44,7 @@ internal class CurrencyStatusOperations( priceChange = quote?.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, + networkAddress = status.address, ) quote == null -> CryptoCurrencyStatus.Loading else -> CryptoCurrencyStatus.Loaded( @@ -52,6 +54,7 @@ internal class CurrencyStatusOperations( priceChange = quote.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, + networkAddress = status.address, ) } } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index aa477fee0c..d01254b1e7 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -85,16 +85,22 @@ internal object MockTokensStates { priceChange = quote.priceChange, pendingTransactions = emptySet(), hasCurrentNetworkTransactions = false, + networkAddress = requireNotNull(networkStatus.value as? NetworkStatus.Verified).address, ), ) } - val noQuotesTokensStatuses = loadedTokensStates.map { currency -> - currency.copy( + val noQuotesTokensStatuses = loadedTokensStates.map { status -> + status.copy( value = CryptoCurrencyStatus.NoQuote( - amount = currency.value.amount!!, + amount = status.value.amount!!, pendingTransactions = emptySet(), hasCurrentNetworkTransactions = false, + networkAddress = requireNotNull( + value = MockNetworks.verifiedNetworksStatuses + .first { it.networkId == status.currency.network.id } + .value as? NetworkStatus.Verified, + ).address, ), ) } diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 48a2eca7c6..0e84af12c3 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(deps.arrow.core) implementation(deps.jodatime) implementation(deps.kotlin.immutable.collections) + implementation(deps.reKotlin) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt index ea88f686dd..597680d5eb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt @@ -40,7 +40,7 @@ internal class WalletCryptoCurrencyActionsConverter( WalletManageButton.Sell(enabled = action.enabled, onClick = clickIntents::onSellClick) } is TokenActionsState.ActionState.Send -> { - WalletManageButton.Send(enabled = action.enabled, onClick = clickIntents::onSellClick) + WalletManageButton.Send(enabled = action.enabled, onClick = clickIntents::onSendClick) } is TokenActionsState.ActionState.Swap -> null } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index d27c5d8ba9..288a73f73e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -16,10 +16,13 @@ import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Network @@ -78,6 +81,7 @@ internal class WalletViewModel @Inject constructor( private val unlockWalletsUseCase: UnlockWalletsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, + private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents { @@ -111,6 +115,7 @@ internal class WalletViewModel @Inject constructor( var uiState: WalletState by uiStateHolder(initialState = stateFactory.getInitialState()) private var wallets: List by Delegates.notNull() + private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private val tokensJobHolder = JobHolder() private val marketPriceJobHolder = JobHolder() @@ -219,11 +224,13 @@ internal class WalletViewModel @Inject constructor( private fun updateMarketPrice(userWalletId: UserWalletId, isRefreshing: Boolean) { getPrimaryCurrencyUseCase(userWalletId = userWalletId) .distinctUntilChanged() - .onEach { + .onEach { either -> uiState = stateFactory.getSingleCurrencyLoadedBalanceState( - cryptoCurrencyEither = it, + cryptoCurrencyEither = either, isRefreshing = isRefreshing, ) + + either.onRight { status -> cryptoCurrencyStatus = status } } .flowOn(dispatchers.io) .launchIn(viewModelScope) @@ -422,11 +429,21 @@ internal class WalletViewModel @Inject constructor( } override fun onBuyClick() { - // TODO: [REDACTED_JIRA] + val state = uiState as? WalletState.ContentState ?: return + val status = cryptoCurrencyStatus ?: return + val wallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) + + reduxStateHolder.dispatch( + TradeCryptoAction.New.Buy( + userWallet = wallet, + cryptoCurrencyStatus = status, + appCurrencyCode = selectedAppCurrencyFlow.value.code, + ), + ) } override fun onSendClick() { - // TODO: [REDACTED_JIRA] + reduxStateHolder.dispatch(TradeCryptoAction.New.Send) } override fun onReceiveClick() { @@ -434,7 +451,14 @@ internal class WalletViewModel @Inject constructor( } override fun onSellClick() { - // TODO: [REDACTED_JIRA] + val status = cryptoCurrencyStatus ?: return + + reduxStateHolder.dispatch( + TradeCryptoAction.New.Sell( + cryptoCurrencyStatus = status, + appCurrencyCode = selectedAppCurrencyFlow.value.code, + ), + ) } override fun onReloadClick() {