diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aa64c0d699..3cf379b69f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,9 @@ configurations.all { substitute(module("com.facebook.react:hermes-engine")) .using(module("com.facebook.react:hermes-android:0.72.4")) + + substitute(module("org.bouncycastle:bcprov-jdk15on")) + .using(module("org.bouncycastle:bcprov-jdk18on:1.73")) } force( diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 32e44932c0..99e803e45b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -120,7 +120,7 @@ diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 6be450bd32..b09a1ffa91 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -434,6 +434,16 @@ } ] }, + { + "id" : "algorand", + "symbol" : "ALGO", + "name" : "Algorand", + "networks" : [ + { + "networkId" : "algorand/test" + } + ] + }, { "id" : "arbitrum-one", "symbol" : "ETH", @@ -569,6 +579,16 @@ } ] }, + { + "id": "shibarium", + "name": "Shibarium", + "symbol": "BONE", + "networks": [ + { + "networkId": "shibarium/test" + } + ] + }, { "id": "aptos", "name": "Aptos", @@ -578,6 +598,16 @@ "networkId": "aptos/test" } ] + }, + { + "id": "hedera-hashgraph", + "name": "Hedera", + "symbol": "HBAR", + "networks": [ + { + "networkId": "hedera-hashgraph/test" + } + ] } ] } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 4ef519edbd..04b3f17541 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -37,7 +37,7 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.features.managetokens.navigation.ManageTokensRouter +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -126,7 +126,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lateinit var tokenDetailsRouter: TokenDetailsRouter @Inject - lateinit var manageTokensRouter: ManageTokensRouter + lateinit var manageTokensUi: ManageTokensUi @Inject lateinit var walletConnectInteractor: WalletConnectInteractor @@ -213,7 +213,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac walletRouter = walletRouter, walletConnectInteractor = walletConnectInteractor, tokenDetailsRouter = tokenDetailsRouter, - manageTokensRouter = manageTokensRouter, + manageTokensUi = manageTokensUi, cardSdkConfigRepository = cardSdkConfigRepository, sendRouter = sendRouter, qrScanningRouter = qrScanningRouter, diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index f892dddee4..e4337c01f6 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -10,6 +10,8 @@ import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.Logger import com.tangem.Log import com.tangem.LogFormat +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.filter.OneTimeEventFilter @@ -28,7 +30,6 @@ import com.tangem.domain.apptheme.GetAppThemeModeUseCase import com.tangem.domain.apptheme.repository.AppThemeModeRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor -import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.common.LogConfig import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository @@ -37,10 +38,8 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles -import com.tangem.features.tester.api.TesterFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler @@ -118,9 +117,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var scanCardProcessor: ScanCardProcessor - @Inject - lateinit var blockchainExceptionHandler: BlockchainExceptionHandler - @Inject lateinit var appCurrencyRepository: AppCurrencyRepository @@ -155,10 +151,10 @@ internal class TapApplication : Application(), ImageLoaderFactory { lateinit var oneTimeEventFilter: OneTimeEventFilter @Inject - lateinit var derivationsRepository: DerivationsRepository + lateinit var blockchainDataStorage: BlockchainDataStorage @Inject - lateinit var testerFeatureToggles: TesterFeatureToggles + lateinit var accountCreator: AccountCreator // endregion Injected override fun onCreate() { @@ -234,8 +230,8 @@ internal class TapApplication : Application(), ImageLoaderFactory { balanceHidingRepository = balanceHidingRepository, walletsRepository = walletsRepository, sendFeatureToggles = sendFeatureToggles, - derivationsRepository = derivationsRepository, - testerFeatureToggles = testerFeatureToggles, + blockchainDataStorage = blockchainDataStorage, + accountCreator = accountCreator, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt index a913befc98..08f62eee72 100644 --- a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt +++ b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt @@ -1,6 +1,8 @@ package com.tangem.tap.common import android.content.Context +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent @@ -22,6 +24,10 @@ class CustomTabsManager { if (MutableAppThemeModeHolder.isDarkThemeActive) COLOR_SCHEME_DARK else COLOR_SCHEME_LIGHT, ) .build() + + // Open CustomTabsActivity as new task without saving into the stack + customTabsIntent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY) + customTabsIntent.launchUrl(context, Uri.parse(url)) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index 13c04ec76f..ed47afa57c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -1,7 +1,6 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.tap.common.extensions.filterNotNull /** [REDACTED_AUTHOR] @@ -42,17 +41,22 @@ sealed class WalletConnect( val methodName: String, val blockchain: String, val errorCode: String? = null, + val errorDescription: String? = null, ) { fun toParamsMap(): Map { val validation = if (errorCode == null) Validation.SUCCESS.param else Validation.FAIL.param - return mapOf( - AnalyticsParam.DAPP_NAME to dAppName, - AnalyticsParam.DAPP_URL to dAppUrl, - AnalyticsParam.METHOD_NAME to methodName, - AnalyticsParam.BLOCKCHAIN to blockchain, - AnalyticsParam.VALIDATION to validation, - if (errorCode != null) AnalyticsParam.ERROR_CODE to errorCode else null to null, - ).filterNotNull() + val code = errorCode ?: SUCCESS_CODE + return buildMap { + put(AnalyticsParam.DAPP_NAME, dAppName) + put(AnalyticsParam.DAPP_URL, dAppUrl) + put(AnalyticsParam.METHOD_NAME, methodName) + put(AnalyticsParam.BLOCKCHAIN, blockchain) + put(AnalyticsParam.VALIDATION, validation) + put(AnalyticsParam.ERROR_CODE, code) + if (errorDescription != null) { + put(AnalyticsParam.ERROR_DESCRIPTION, errorDescription) + } + } } } @@ -60,4 +64,8 @@ sealed class WalletConnect( SUCCESS("Success"), FAIL("Fail"), } + + companion object { + private const val SUCCESS_CODE = "0" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 6dc414e945..fa0d174bf9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -151,18 +151,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.ResetToFactory -> ResetCardFragment() AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment() AppScreen.Disclaimer -> DisclaimerFragment() - AppScreen.ManageTokens -> { - val featureToggles = store.state.daggerGraphState.get( - getDependency = DaggerGraphState::manageTokensFeatureToggles, - ) - if (featureToggles.isRedesignedScreenEnabled) { - store.state.daggerGraphState - .get(getDependency = DaggerGraphState::manageTokensRouter) - .getEntryFragment() - } else { - TokensListFragment() - } - } + AppScreen.ManageTokens -> TokensListFragment() AppScreen.AddCustomToken -> AddCustomTokenFragment() AppScreen.WalletDetails -> { store.state.daggerGraphState diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index e312758a52..892271058e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -33,7 +33,6 @@ import com.tangem.tap.features.shop.redux.ShopMiddleware import com.tangem.tap.features.shop.redux.ShopState import com.tangem.tap.features.signin.redux.SignInMiddleware import com.tangem.tap.features.signin.redux.SignInState -import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware import com.tangem.tap.features.tokens.legacy.redux.TokensState import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware import com.tangem.tap.features.welcome.redux.WelcomeMiddleware @@ -91,7 +90,6 @@ data class AppState( SendMiddleware().sendMiddleware, DetailsMiddleware().detailsMiddleware, DisclaimerMiddleware().disclaimerMiddleware, - TokensMiddleware.tokensMiddleware, WalletConnectMiddleware().walletConnectMiddleware, BackupMiddleware().backupMiddleware, ShopMiddleware().shopMiddleware, diff --git a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt index 64aeea2d59..9131ec1023 100644 --- a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt +++ b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt @@ -77,7 +77,7 @@ class TruncateStart : BaseTruncate() { class TruncateMiddle : BaseTruncate() { override fun roughTruncate(text: String, residualLength: Int): String { - if (text.length <= residualLength) return text + if (text.length <= residualLength || residualLength < 0) return text hasBeenTruncated = true val halfOfResidualLength = residualLength / 2 diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 0d32faca9a..76aa092682 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -8,8 +8,6 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.tap.domain.TangemSdkManager -import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -62,14 +60,8 @@ internal object CardDomainModule { @Provides @ViewModelScoped - fun provideDerivePublicKeysUseCase( - tangemSdkManager: TangemSdkManager, - derivationsRepository: DerivationsRepository, - ): DerivePublicKeysUseCase { - return DefaultDerivePublicKeysUseCase( - tangemSdkManager = tangemSdkManager, - derivationsRepository = derivationsRepository, - ) + fun provideDerivePublicKeysUseCase(derivationsRepository: DerivationsRepository): DerivePublicKeysUseCase { + return DerivePublicKeysUseCase(derivationsRepository = derivationsRepository) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 7149fc13da..c67960a860 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -99,6 +99,7 @@ internal object TokensDomainModule { networksRepository: NetworksRepository, marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, swapRepository: SwapRepository, + currencyChecksRepository: CurrencyChecksRepository, showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, promoRepository: PromoRepository, dispatchers: CoroutineDispatcherProvider, @@ -109,6 +110,7 @@ internal object TokensDomainModule { quotesRepository = quotesRepository, networksRepository = networksRepository, marketCryptoCurrencyRepository = marketCryptoCurrencyRepository, + currencyChecksRepository = currencyChecksRepository, swapRepository = swapRepository, showSwapPromoTokenUseCase = showSwapPromoTokenUseCase, promoRepository = promoRepository, diff --git a/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt index 61170463f0..479be1cc3c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt @@ -1,18 +1,34 @@ package com.tangem.tap.di.domain import com.tangem.domain.visa.GetVisaCurrencyUseCase +import com.tangem.domain.visa.GetVisaTxDetailsUseCase +import com.tangem.domain.visa.GetVisaTxHistoryUseCase import com.tangem.domain.visa.repository.VisaRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped @Module @InstallIn(ViewModelComponent::class) internal object VisaDomainModule { @Provides + @ViewModelScoped fun provideVisaCurrencyUseCase(visaRepository: VisaRepository): GetVisaCurrencyUseCase { return GetVisaCurrencyUseCase(visaRepository) } + + @Provides + @ViewModelScoped + fun provideGetVisaTxHistoryUseCase(visaRepository: VisaRepository): GetVisaTxHistoryUseCase { + return GetVisaTxHistoryUseCase(visaRepository) + } + + @Provides + @ViewModelScoped + fun provideGetVisaTxDetailsUseCase(visaRepository: VisaRepository): GetVisaTxDetailsUseCase { + return GetVisaTxDetailsUseCase(visaRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt index 02a52ce342..3fedeef4ca 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt @@ -1,6 +1,8 @@ package com.tangem.tap.di.domain import com.squareup.moshi.Moshi +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.di.SdkMoshi @@ -25,6 +27,8 @@ internal object WalletManagersFacadeModule { walletManagersStore: WalletManagersStore, userWalletsStore: UserWalletsStore, configManager: ConfigManager, + blockchainDataStorage: BlockchainDataStorage, + accountCreator: AccountCreator, mnemonicRepository: MnemonicRepository, assetReader: AssetReader, @SdkMoshi moshi: Moshi, @@ -33,9 +37,11 @@ internal object WalletManagersFacadeModule { walletManagersStore = walletManagersStore, userWalletsStore = userWalletsStore, configManager = configManager, + blockchainDataStorage = blockchainDataStorage, assetReader = assetReader, moshi = moshi, mnemonic = mnemonicRepository.generateDefaultMnemonic(), + accountCreator = accountCreator, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 8d9ba42e6a..24db8bcbc8 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -5,19 +5,20 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation -import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -40,8 +41,13 @@ class TapWalletManager( field = value } - val walletManagerFactory: WalletManagerFactory - by lazy { WalletManagerFactory(blockchainSdkConfig) } + val walletManagerFactory: WalletManagerFactory by lazy { + WalletManagerFactory( + config = blockchainSdkConfig, + accountCreator = store.state.daggerGraphState.get(DaggerGraphState::accountCreator), + blockchainDataStorage = store.state.daggerGraphState.get(DaggerGraphState::blockchainDataStorage), + ) + } suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) { // If a previous job was running, it gets cancelled before the new one starts, diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt deleted file mode 100644 index a09df2969a..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.tap.domain.card - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.card.repository.DerivationsRepository -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.operations.derivation.DerivationTaskResponse -import com.tangem.tap.domain.TangemSdkManager - -internal class DefaultDerivePublicKeysUseCase( - private val tangemSdkManager: TangemSdkManager, - private val derivationsRepository: DerivationsRepository, -) : DerivePublicKeysUseCase { - - override suspend fun invoke( - cardId: String?, - derivations: Map>, - ): Either { - tangemSdkManager.derivePublicKeys(cardId = cardId, derivations = derivations) - .doOnSuccess { return it.right() } - .doOnFailure { return Unit.left() } - - return Unit.left() - } - - override suspend fun invoke( - userWalletId: UserWalletId, - currencies: List, - ): Either { - return Either.catch { - derivationsRepository.derivePublicKeys(userWalletId, currencies) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt index ab1355fae2..4f37c03839 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt @@ -352,7 +352,8 @@ class WalletConnectRepositoryImpl @Inject constructor( dAppUrl = session?.url ?: "", methodName = requestData.method, blockchain = requestData.blockchain, - errorCode = WalletConnectError.ValidationError.toString(), + errorCode = WalletConnectError.ValidationError.error, + errorDescription = it.throwable.message, ) }, ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt index ca01b701a7..efb5215bd7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt @@ -1,12 +1,24 @@ package com.tangem.tap.domain.walletconnect2.domain.models -sealed class WalletConnectError : Exception() { - data class ApprovalErrorMissingNetworks(val missingChains: List) : WalletConnectError() - data class ApprovalErrorAddNetwork(val networks: List) : WalletConnectError() - data class ApprovalErrorUnsupportedNetwork(val unsupportedNetworks: List) : WalletConnectError() - data class ExternalApprovalError(override val message: String?) : WalletConnectError() - object WrongUserWallet : WalletConnectError() - object UnsupportedMethod : WalletConnectError() - object SigningError : WalletConnectError() - object ValidationError : WalletConnectError() +sealed class WalletConnectError(val error: String) : Exception() { + data class ApprovalErrorMissingNetworks( + val missingChains: List, + ) : WalletConnectError("ApprovalErrorMissingNetworks") + + data class ApprovalErrorAddNetwork( + val networks: List, + ) : WalletConnectError("ApprovalErrorAddNetwork") + + data class ApprovalErrorUnsupportedNetwork( + val unsupportedNetworks: List, + ) : WalletConnectError("ApprovalErrorUnsupportedNetwork") + + data class ExternalApprovalError( + override val message: String?, + ) : WalletConnectError("ExternalApprovalError") + + object WrongUserWallet : WalletConnectError("WrongUserWallet") + object UnsupportedMethod : WalletConnectError("UnsupportedMethod") + object SigningError : WalletConnectError("SigningError") + object ValidationError : WalletConnectError("ValidationError") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt index 44d4a98284..61dc90c2dc 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.customtoken.impl.di import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.features.tester.api.TesterFeatureToggles import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor @@ -30,7 +29,6 @@ internal object CustomTokenInteractorModule { reduxStateHolder: AppStateHolder, getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, derivePublicKeysUseCase: DerivePublicKeysUseCase, - testerFeatureToggles: TesterFeatureToggles, ): CustomTokenInteractor { return DefaultCustomTokenInteractor( featureRepository = DefaultCustomTokenRepository( @@ -40,7 +38,6 @@ internal object CustomTokenInteractorModule { ), getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, derivePublicKeysUseCase = derivePublicKeysUseCase, - testerFeatureToggles = testerFeatureToggles, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt index 2d6ad2aebf..f3f73a98d3 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt @@ -1,40 +1,19 @@ package com.tangem.tap.features.customtoken.impl.domain -import com.tangem.blockchain.blockchains.cardano.CardanoUtils import com.tangem.blockchain.common.Blockchain -import com.tangem.common.CompletionResult -import com.tangem.common.card.EllipticCurve -import com.tangem.common.core.TangemError -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.extensions.toMapKey -import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.common.configs.CardConfig import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.features.tester.api.TesterFeatureToggles -import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.Currency import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken -import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager -import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE -import kotlinx.coroutines.delay import timber.log.Timber /** @@ -48,7 +27,6 @@ class DefaultCustomTokenInteractor( private val featureRepository: CustomTokenRepository, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val testerFeatureToggles: TesterFeatureToggles, ) : CustomTokenInteractor { // TODO: Move to DI @@ -70,136 +48,12 @@ class DefaultCustomTokenInteractor( val userWallet = getSelectedWalletSyncUseCase().fold(ifLeft = { return }, ifRight = { it }) val currency = Currency.fromCustomCurrency(customCurrency) - if (testerFeatureToggles.isDerivePublicKeysRefactoringEnabled) { - val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse)) - derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies) - .onRight { - addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies) - } - .onLeft { Timber.e("Failed to derive public keys: $it") } - } else { - // TODO: delete [REDACTED_JIRA] - val isNeedToDerive = isNeedToDerive(userWallet, currency) - if (isNeedToDerive) { - deriveMissingBlockchains( - userWallet = userWallet, - currencyList = listOf(currency), - onSuccess = { submitAdd(userWallet = userWallet.copy(scanResponse = it), currency = currency) }, - ) { - throw it - } - } else { - submitAdd(userWallet, currency) + val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse)) + derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies) + .onRight { + addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies) } - } - } - - private fun isNeedToDerive(userWallet: UserWallet, currency: Currency): Boolean { - val scanResponse = userWallet.scanResponse - return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false - } - - private suspend fun deriveMissingBlockchains( - userWallet: UserWallet, - currencyList: List, - onSuccess: suspend (ScanResponse) -> Unit, - onFailure: suspend (TangemError) -> Unit, - ) { - val scanResponse = userWallet.scanResponse - val config = CardConfig.createConfig(scanResponse.card) - val derivationDataList = currencyList.mapNotNull { currency -> - val curve = config.primaryCurve(currency.blockchain) - curve?.let { getDerivations(curve, scanResponse, currency) } - } - - val derivations = buildMap> { - derivationDataList.forEach { - val current = this[it.derivations.first] - if (current != null) { - current.addAll(it.derivations.second) - current.distinct() - } else { - this[it.derivations.first] = it.derivations.second.toMutableList() - } - } - } - if (derivations.isEmpty()) { - onSuccess(scanResponse) - return - } - - when (val result = tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)) { - is CompletionResult.Success -> { - val newDerivedKeys = result.data.entries - val oldDerivedKeys = scanResponse.derivedKeys - - val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() - - val updatedDerivedKeys = walletKeys.associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) - val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) - ExtendedPublicKeysMap(oldDerivations + newDerivations) - } - - val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys) - store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) - delay(DELAY_SDK_DIALOG_CLOSE) - - onSuccess(updatedScanResponse) - } - is CompletionResult.Failure -> { - onFailure.invoke(result.error) - store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens")) - } - } - } - - private fun getDerivations( - curve: EllipticCurve, - scanResponse: ScanResponse, - currency: Currency, - ): TokensMiddleware.DerivationData? { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - - val supportedCurves = currency.blockchain.getSupportedCurves() - val path = currency.blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) - .takeIf { supportedCurves.contains(curve) } - - val customPath = currency.derivationPath?.let { - DerivationPath(it) - }.takeIf { supportedCurves.contains(curve) } - - val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() - if (bothCandidates.isEmpty()) return null - - if (currency is Currency.Blockchain && currency.blockchain == Blockchain.Cardano) { - currency.derivationPath?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } - } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys: ExtendedPublicKeysMap = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) - val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - - val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } - if (toDerive.isEmpty()) return null - - return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) - } - - private suspend fun submitAdd(userWallet: UserWallet, currency: Currency) { - val scanResponse = userWallet.scanResponse - val userWalletId = userWallet.walletId - - val currencyList = listOfNotNull(element = currency.toCryptoCurrency(scanResponse)) - - userWalletsListManager.update(userWalletId) { - it.copy(scanResponse = scanResponse) - } - - addCryptoCurrenciesUseCase(userWalletId, currencyList) + .onLeft { Timber.e("Failed to derive public keys: $it") } } private fun Currency.toCryptoCurrency(scanResponse: ScanResponse): CryptoCurrency? { diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContactAddressValidator.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt similarity index 87% rename from app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContactAddressValidator.kt rename to app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt index b522a922b1..5db91d6354 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContactAddressValidator.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt @@ -3,21 +3,21 @@ package com.tangem.tap.features.customtoken.impl.presentation.validators import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressService import com.tangem.common.card.EllipticCurve -import com.tangem.domain.AddCustomTokenError +import com.tangem.domain.tokens.error.AddCustomTokenError /** * Validator of contract address * [REDACTED_AUTHOR] */ -object ContactAddressValidator { +object ContractAddressValidator { /** Validate a [address] using [blockchain] */ fun validate(address: String, blockchain: Blockchain): ContractAddressValidatorResult { return when { - address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FieldIsEmpty) + address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FIELD_IS_EMPTY) validateAddress(blockchain, address) -> ContractAddressValidatorResult.Success - else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.InvalidContractAddress) + else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.INVALID_CONTRACT_ADDRESS) } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt index 417b3821e0..daf1b4d159 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.customtoken.impl.presentation.validators -import com.tangem.domain.AddCustomTokenError +import com.tangem.domain.tokens.error.AddCustomTokenError /** * Result of validation contract address diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index fae60b48c3..0825668edb 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -16,13 +16,13 @@ import com.tangem.blockchain.common.derivation.DerivationStyle import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.HDWalletError -import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.extensions.* import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase +import com.tangem.domain.tokens.error.AddCustomTokenError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor @@ -33,7 +33,7 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField.SelectorItem import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder -import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator +import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidator import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -461,11 +461,11 @@ internal class AddCustomTokenViewModel @Inject constructor( private fun getTokenWarningSet(): Set { val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain - val isContractAddressFieldEmpty = ContactAddressValidator.validate( + val isContractAddressFieldEmpty = ContractAddressValidator.validate( address = uiState.form.contractAddressInputField.value, blockchain = networkSelectorValue, ).let { - it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FieldIsEmpty + it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FIELD_IS_EMPTY } val isSupportedToken = if (!isNetworkSelected()) { @@ -513,7 +513,7 @@ internal class AddCustomTokenViewModel @Inject constructor( val state = when { isAllTokenFieldsFilled() && isNetworkSelected() -> { val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain - val error = ContactAddressValidator.validate( + val error = ContractAddressValidator.validate( address = uiState.form.contractAddressInputField.value, blockchain = networkSelectorValue, ) @@ -621,7 +621,7 @@ internal class AddCustomTokenViewModel @Inject constructor( private fun handleContractAddressErrorValidation(type: AddCustomTokenError) { when { - isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> { + isNetworkSelected() && type == AddCustomTokenError.INVALID_CONTRACT_ADDRESS -> { val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled() uiState = uiState.copySealed( form = uiState.form.copy( @@ -644,7 +644,7 @@ internal class AddCustomTokenViewModel @Inject constructor( ) } - !isNetworkSelected() || type == AddCustomTokenError.FieldIsEmpty -> { + !isNetworkSelected() || type == AddCustomTokenError.FIELD_IS_EMPTY -> { uiState = uiState.copySealed( form = uiState.form.copy( contractAddressInputField = uiState.form.contractAddressInputField.copy(isError = false), @@ -727,7 +727,7 @@ internal class AddCustomTokenViewModel @Inject constructor( ) val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain - val validatorResult = ContactAddressValidator.validate( + val validatorResult = ContractAddressValidator.validate( address = enteredValue, blockchain = selectedNetwork, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 79a40b22a9..73a454fe10 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -170,7 +170,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M .padding(horizontal = TangemTheme.dimens.spacing16) .align(Alignment.BottomCenter) .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { AnimatedVisibility( visible = config.currentStory == Stories.Currencies, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index aebf48b161..0533961886 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.material.ButtonColors import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -13,15 +12,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.SpacerW8 -import com.tangem.core.ui.components.buttons.common.TangemButton -import com.tangem.core.ui.components.buttons.common.TangemButtonColors +import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R -@Suppress("MagicNumber") @Composable internal fun HomeButtons( btnScanStateInProgress: Boolean, @@ -38,7 +33,7 @@ internal fun HomeButtons( showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) - SpacerW8() + SpacerW12() OrderCardButton( modifier = Modifier.weight(weight = 1f), onClick = onShopButtonClick, @@ -48,44 +43,26 @@ internal fun HomeButtons( @Composable private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { - TangemButton( + StoriesButton( modifier = modifier, text = stringResource(id = R.string.home_button_scan), + useDarkerColors = false, icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24), - colors = LightBgScanCardButtonColors, - showProgress = showProgress, - enabled = true, onClick = onClick, + showProgress = showProgress, ) } @Composable private fun OrderCardButton(onClick: () -> Unit, modifier: Modifier = Modifier) { - TangemButton( + StoriesButton( modifier = modifier, text = stringResource(id = R.string.home_button_order), - icon = TangemButtonIconPosition.None, - colors = LightBgOrderCardButtonColors, - showProgress = false, - enabled = true, + useDarkerColors = true, onClick = onClick, ) } -private val LightBgScanCardButtonColors: ButtonColors = TangemButtonColors( - backgroundColor = TangemColorPalette.Light4, - contentColor = TangemColorPalette.Dark6, - disabledBackgroundColor = TangemColorPalette.Dark5, - disabledContentColor = TangemColorPalette.Dark6, -) - -private val LightBgOrderCardButtonColors: ButtonColors = TangemButtonColors( - backgroundColor = TangemColorPalette.Dark6, - contentColor = TangemColorPalette.White, - disabledBackgroundColor = TangemColorPalette.Dark6, - disabledContentColor = TangemColorPalette.White, -) - // region Preview @Preview(showBackground = true, widthDp = 360) @Composable @@ -95,10 +72,10 @@ private fun HomeButtonsPreview(@PreviewParameter(HomeButtonsParameterProvider::c modifier = Modifier.background(Color.Black), ) { HomeButtons( - modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), btnScanStateInProgress = state.btnScanStateInProgress, onScanButtonClick = {}, onShopButtonClick = {}, + modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt index 722dd4f481..4e91ce98bc 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt @@ -1,31 +1,42 @@ package com.tangem.tap.features.home.compose.views -import androidx.compose.material.ButtonColors +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import com.tangem.core.ui.components.buttons.common.TangemButton -import com.tangem.core.ui.components.buttons.common.TangemButtonColors +import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @Composable internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) { - TangemButton( + StoriesButton( modifier = modifier, text = stringResource(id = R.string.common_search_tokens), icon = TangemButtonIconPosition.Start(R.drawable.ic_search_24), - onClick = onClick, - colors = SearchCurrenciesButtonColors, showProgress = false, - enabled = true, + useDarkerColors = true, + onClick = onClick, ) } -private val SearchCurrenciesButtonColors: ButtonColors = TangemButtonColors( - backgroundColor = TangemColorPalette.Dark5, - contentColor = TangemColorPalette.White, - disabledBackgroundColor = TangemColorPalette.Dark5, - disabledContentColor = TangemColorPalette.White, -) \ No newline at end of file +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SearchCurrenciesButtonPreview() { + TangemTheme { + Box( + modifier = Modifier + .background(color = Color.Black) + .padding(all = TangemTheme.dimens.spacing16), + ) { + SearchCurrenciesButton(modifier = Modifier.fillMaxWidth(), onClick = {}) + } + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesButton.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesButton.kt new file mode 100644 index 0000000000..45b8745cca --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesButton.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.features.home.compose.views + +import androidx.compose.material.ButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonColors +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun StoriesButton( + text: String, + useDarkerColors: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + icon: TangemButtonIconPosition = TangemButtonIconPosition.None, + showProgress: Boolean = false, +) { + TangemButton( + modifier = modifier, + text = text, + icon = icon, + colors = if (useDarkerColors) DarkerButtonColors else LighterButtonColors, + showProgress = showProgress, + enabled = true, + shape = TangemTheme.shapes.roundedCornersXMedium, + iconPadding = when (icon) { + is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing4 + is TangemButtonIconPosition.End, + is TangemButtonIconPosition.None, + -> TangemTheme.dimens.spacing8 + }, + onClick = onClick, + ) +} + +private val LighterButtonColors: ButtonColors = TangemButtonColors( + backgroundColor = TangemColorPalette.Light4, + contentColor = TangemColorPalette.Dark6, + disabledBackgroundColor = TangemColorPalette.Dark5, + disabledContentColor = TangemColorPalette.Dark6, +) + +private val DarkerButtonColors: ButtonColors = TangemButtonColors( + backgroundColor = TangemColorPalette.Dark4, + contentColor = TangemColorPalette.White, + disabledBackgroundColor = TangemColorPalette.Dark4, + disabledContentColor = TangemColorPalette.White, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt index a8a1fd7bc1..ef3514bce4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt @@ -13,10 +13,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerW4 +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import kotlinx.coroutines.delay @@ -30,7 +30,7 @@ fun StoriesProgressBar( stepDuration: Int = 8_000, onStepFinish: () -> Unit = {}, ) { - val progress = remember(currentStep) { Animatable(0f) } + val progress = remember(currentStep) { Animatable(initialValue = 0f) } val context = LocalContext.current val animatorSpeed = Settings.Global.getFloat( @@ -75,20 +75,21 @@ fun StoriesProgressBar( .height(TangemTheme.dimens.size2) .weight(1f) .clip(RoundedCornerShape(TangemTheme.dimens.radius2)) - .background(Color.White.copy(alpha = 0.4f)), + .background(TangemColorPalette.White.copy(alpha = .2f)), ) { Box( modifier = Modifier .clip(RoundedCornerShape(TangemTheme.dimens.radius2)) - .background(Color.White) - .fillMaxHeight().let { + .background(TangemColorPalette.White) + .fillMaxHeight() + .let { when (index) { currentStep -> it.fillMaxWidth(progress.value) - in 0..currentStep -> it.fillMaxWidth(1f) + in 0..currentStep -> it.fillMaxWidth(fraction = 1f) else -> it } }, - ) {} + ) } if (index != steps) { SpacerW4() @@ -100,5 +101,12 @@ fun StoriesProgressBar( @Preview @Composable private fun StoriesProgressBarPreview() { - StoriesProgressBar(steps = 3, currentStep = 2, paused = false) { } + Box( + modifier = Modifier + .wrapContentSize() + .background(TangemColorPalette.Black) + .padding(vertical = TangemTheme.dimens.spacing16), + ) { + StoriesProgressBar(steps = 5, currentStep = 3, paused = false) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 08f0c090d5..3c3409459e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -59,26 +59,41 @@ sealed class TransactionExtrasAction : SendScreenActionUi { object Release : TransactionExtrasAction() + @Deprecated("Only in legacy send screen") sealed class XlmMemo : TransactionExtrasAction() { // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() data class HandleUserInput(val data: String) : XlmMemo() } + @Deprecated("Only in legacy send screen") sealed class BinanceMemo : TransactionExtrasAction() { data class HandleUserInput(val data: String) : BinanceMemo() } + @Deprecated("Only in legacy send screen") sealed class XrpDestinationTag : TransactionExtrasAction() { data class HandleUserInput(val data: String) : XrpDestinationTag() } + @Deprecated("Only in legacy send screen") sealed class TonMemo : TransactionExtrasAction() { data class HandleUserInput(val data: String) : TonMemo() } + @Deprecated("Only in legacy send screen") sealed class CosmosMemo : TransactionExtrasAction() { data class HandleUserInput(val data: String) : CosmosMemo() } + + @Deprecated("Only in legacy send screen") + sealed class HederaMemo : TransactionExtrasAction() { + data class HandleUserInput(val data: String) : HederaMemo() + } + + @Deprecated("Only in legacy send screen") + sealed class AlgorandMemo : TransactionExtrasAction() { + data class HandleUserInput(val data: String) : AlgorandMemo() + } } sealed class AddressVerifyAction : SendScreenAction { 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 9fba907beb..2c21ea333c 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 @@ -1,8 +1,10 @@ package com.tangem.tap.features.send.redux.middlewares import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras +import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras @@ -171,6 +173,12 @@ private fun sendTransaction( transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionExtras(it)) } transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) } transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) } + transactionExtras.hederaMemoState?.memo?.let { + txData = txData.copy( + extras = HederaTransactionBuilder.HederaTransactionExtras(it), + ) + } + transactionExtras.algorandMemoState?.memo?.let { txData = txData.copy(extras = AlgorandTransactionExtras(it)) } scope.launch { // TODO: Risky commented this part, unknown logic, need to test if removed diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt index cb0fbaa3aa..c781547049 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt @@ -19,6 +19,8 @@ class TransactionExtrasReducer : SendInternalReducer { is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState) is TonMemo -> handleTonMemo(action, sendState, sendState.transactionExtrasState) is CosmosMemo -> handleCosmosMemo(action, sendState, sendState.transactionExtrasState) + is HederaMemo -> handleHederaMemo(action, sendState, sendState.transactionExtrasState) + is AlgorandMemo -> handleAlgorandMemo(action, sendState, sendState.transactionExtrasState) else -> sendState } } @@ -52,6 +54,10 @@ class TransactionExtrasReducer : SendInternalReducer { Blockchain.TerraV1, Blockchain.TerraV2, -> TransactionExtrasState(cosmosMemoState = CosmosMemoState()) + Blockchain.Hedera, Blockchain.HederaTestnet -> TransactionExtrasState(hederaMemoState = HederaMemoState()) + Blockchain.Algorand, Blockchain.AlgorandTestnet -> TransactionExtrasState( + algorandMemoState = AlgorandMemoState(), + ) else -> emptyResult } return updateLastState(sendState.copy(transactionExtrasState = result), result) @@ -162,4 +168,34 @@ class TransactionExtrasReducer : SendInternalReducer { } return updateLastState(sendState.copy(transactionExtrasState = result), result) } + + private fun handleHederaMemo( + action: HederaMemo, + sendState: SendState, + infoState: TransactionExtrasState, + ): SendState { + val result = when (action) { + is HederaMemo.HandleUserInput -> { + val memo = action.data + val input = InputViewValue(memo, true) + infoState.copy(hederaMemoState = HederaMemoState(input, memo)) + } + } + return updateLastState(sendState.copy(transactionExtrasState = result), result) + } + + private fun handleAlgorandMemo( + action: AlgorandMemo, + sendState: SendState, + infoState: TransactionExtrasState, + ): SendState { + val result = when (action) { + is AlgorandMemo.HandleUserInput -> { + val memo = action.data + val input = InputViewValue(memo, true) + infoState.copy(algorandMemoState = AlgorandMemoState(input, memo)) + } + } + return updateLastState(sendState.copy(transactionExtrasState = result), result) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt index b6b65a595f..3310a772ce 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt @@ -23,18 +23,21 @@ data class AddressState( fun isReady(): Boolean = error == null && destinationWalletAddress?.isNotEmpty() ?: false } +@Deprecated("Legacy") data class TransactionExtrasState( val xlmMemo: XlmMemoState? = null, val binanceMemo: BinanceMemoState? = null, val xrpDestinationTag: XrpDestinationTagState? = null, val tonMemoState: TonMemoState? = null, val cosmosMemoState: CosmosMemoState? = null, + val hederaMemoState: HederaMemoState? = null, + val algorandMemoState: AlgorandMemoState? = null, ) : IdStateHolder { override val stateId: StateId = StateId.TRANSACTION_EXTRAS fun isNull(): Boolean { return xlmMemo == null && binanceMemo == null && xrpDestinationTag == null && tonMemoState == null && - cosmosMemoState == null + cosmosMemoState == null && hederaMemoState == null && algorandMemoState == null } fun isEmpty(): Boolean { @@ -43,8 +46,16 @@ data class TransactionExtrasState( val isXrpEmpty = xrpDestinationTag?.viewFieldValue?.value?.isEmpty() ?: false val isTonEmpty = tonMemoState?.viewFieldValue?.value?.isEmpty() ?: false val isCosmosEmpty = cosmosMemoState?.viewFieldValue?.value?.isEmpty() ?: false + val isHederaEmpty = hederaMemoState?.viewFieldValue?.value?.isEmpty() ?: false + val isAlgorandEmpty = algorandMemoState?.viewFieldValue?.value?.isEmpty() ?: false - return isXlmEmpty || isBinanceEmpty || isXrpEmpty || isTonEmpty || isCosmosEmpty + return isXlmEmpty || + isBinanceEmpty || + isXrpEmpty || + isTonEmpty || + isCosmosEmpty || + isHederaEmpty || + isAlgorandEmpty } } @@ -120,6 +131,18 @@ data class CosmosMemoState( val error: TransactionExtraError? = null, ) +data class HederaMemoState( + val viewFieldValue: InputViewValue = InputViewValue(""), + val memo: String? = null, + val error: TransactionExtraError? = null, +) + +data class AlgorandMemoState( + val viewFieldValue: InputViewValue = InputViewValue(""), + val memo: String? = null, + val error: TransactionExtraError? = null, +) + enum class TransactionExtraError { INVALID_DESTINATION_TAG, INVALID_XLM_MEMO, 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 3e8b23a5d3..775a5cc578 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 @@ -234,6 +234,24 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } .onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) } .launchIn(mainScope) + + etHederaMemo.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { + val info = store.state.sendState.transactionExtrasState + info.hederaMemoState?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.HederaMemo.HandleUserInput(it)) } + .launchIn(mainScope) + + etAlgorandMemo.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { + val info = store.state.sendState.transactionExtrasState + info.algorandMemoState?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.AlgorandMemo.HandleUserInput(it)) } + .launchIn(mainScope) } private fun onCodeScanned(scannedCode: String) { diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 927468b7ea..81899d8e65 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -62,7 +62,8 @@ internal class SendStateSubscriber( } } - @Suppress("ComplexMethod") + @Deprecated("Legacy") + @Suppress("ComplexMethod", "LongMethod") private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) = with(fg.binding.lSendAddress) { fun showView(view: View, info: Any?) { @@ -73,6 +74,8 @@ internal class SendStateSubscriber( showView(binanceMemoContainer, infoState.binanceMemo) showView(tonMemoContainer, infoState.tonMemoState) showView(cosmosMemoContainer, infoState.cosmosMemoState) + showView(hederaMemoContainer, infoState.hederaMemoState) + showView(algorandMemoContainer, infoState.algorandMemoState) infoState.xlmMemo?.let { if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value) @@ -129,6 +132,26 @@ internal class SendStateSubscriber( etCosmosMemo.setText(it.viewFieldValue.value) } } + infoState.hederaMemoState?.let { + if (infoState.hederaMemoState.error != null) { + tilHederaMemo.error = fg.getText(R.string.send_extras_error_invalid_memo) + } else { + tilHederaMemo.error = null + } + if (!it.viewFieldValue.isFromUserInput) { + etHederaMemo.setText(it.viewFieldValue.value) + } + } + infoState.algorandMemoState?.let { + if (infoState.algorandMemoState.error != null) { + tilAlgorandMemo.error = fg.getText(R.string.send_extras_error_invalid_memo) + } else { + tilAlgorandMemo.error = null + } + if (!it.viewFieldValue.isFromUserInput) { + etAlgorandMemo.setText(it.viewFieldValue.value) + } + } } @Suppress("ComplexMethod") diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index ea2c77ed2a..d5328878eb 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -83,7 +83,9 @@ internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modi Icon( painter = painterResource(id = model.iconResId.value), contentDescription = null, - modifier = Modifier.size(size = TangemTheme.dimens.size20), + modifier = Modifier + .size(size = TangemTheme.dimens.size20) + .clip(CircleShape), tint = if (isAdded) Color.Unspecified else TangemTheme.colors.text.tertiary, ) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt index f5481080e5..fafeb5d244 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt @@ -80,6 +80,13 @@ object TokenListPreviewData { iconResId = mutableStateOf(R.drawable.ic_bsc_16), isMainNetwork = false, ), + // check icon clipping + NetworkItemState.ReadContent( + name = "SHIBARIUM", + protocolName = "BEP20", + iconResId = mutableStateOf(R.drawable.ic_shibarium_22), + isMainNetwork = false, + ), ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt index 7ccf604f5a..f4f5e5e72d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt @@ -3,14 +3,20 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels import arrow.core.Either import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.core.navigation.NavigationAction import com.tangem.data.tokens.utils.CryptoCurrencyFactory +import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain -import com.tangem.domain.tokens.TokensAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import timber.log.Timber import kotlin.properties.Delegates @@ -24,6 +30,8 @@ import kotlin.properties.Delegates internal class TokensListMigration( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase, + private val derivePublicKeysUseCase: DerivePublicKeysUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, ) { private var currentNewCoins: List by Delegates.notNull() @@ -76,31 +84,72 @@ internal class TokensListMigration( } } - fun onSaveButtonClick( + suspend fun onSaveButtonClick( changedTokensList: MutableList, changedBlockchainList: List, ) { - store.dispatch( - action = TokensAction.SaveChanges( - currentTokens = currentNewTokens, - currentCoins = currentNewCoins, - changedTokens = changedTokensList.mapNotNull { - cryptoCurrencyFactory.createToken( - sdkToken = it.token, - blockchain = it.blockchain, - extraDerivationPath = null, - derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, - ) - }, - changedCoins = changedBlockchainList.mapNotNull { - cryptoCurrencyFactory.createCoin( - blockchain = it, - extraDerivationPath = null, - derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, - ) - }, - userWallet = currentUserWallet, - ), + val changedTokens = changedTokensList.mapNotNull { + cryptoCurrencyFactory.createToken( + sdkToken = it.token, + blockchain = it.blockchain, + extraDerivationPath = null, + derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, + ) + } + + val changedCoins = changedBlockchainList.mapNotNull { + cryptoCurrencyFactory.createCoin( + blockchain = it, + extraDerivationPath = null, + derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, + ) + } + + val blockchainsToAdd = changedCoins.filterNot(currentNewCoins::contains) + val blockchainsToRemove = currentNewCoins.filterNot(changedCoins::contains) + + val tokensToAdd = changedTokens.filterNot(currentNewTokens::contains) + val tokensToRemove = currentNewTokens.filterNot { token -> changedTokens.any { it == token } } + + removeCurrenciesIfNeeded( + userWalletId = currentUserWallet.walletId, + currencies = blockchainsToRemove + tokensToRemove, + ) + + val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty() + val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() + if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) { + store.dispatchDebugErrorNotification(message = "Nothing to save") + store.dispatchOnMain(NavigationAction.PopBackTo()) + return + } + + val currencyList = blockchainsToAdd + tokensToAdd + + derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) + .onRight { + addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) + store.dispatchOnMain(NavigationAction.PopBackTo()) + } + .onLeft { Timber.e("Failed to derive public keys: $it") } + } + + private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List) { + if (currencies.isEmpty()) return + val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository) + val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade) + + currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies) + + walletManagersFacade.remove( + userWalletId = userWalletId, + networks = currencies + .filterIsInstance() + .mapTo(hashSetOf(), CryptoCurrency::network), + ) + walletManagersFacade.removeTokens( + userWalletId = userWalletId, + tokens = currencies.filterIsInstance().toSet(), ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index 94c4974fc7..337e8f24ec 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -13,12 +13,14 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getGreyedOutIconRes +import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.canHandleBlockchain import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.supportedTokens import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -67,6 +69,8 @@ internal class TokensListViewModel @Inject constructor( private val router: TokensListRouter, private val dispatchers: AppCoroutineDispatcherProvider, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + derivePublicKeysUseCase: DerivePublicKeysUseCase, + addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, analyticsEventHandler: AnalyticsEventHandler, getCurrenciesUseCase: GetCryptoCurrenciesUseCase, ) : ViewModel(), DefaultLifecycleObserver { @@ -88,6 +92,8 @@ internal class TokensListViewModel @Inject constructor( private val tokensListMigration = TokensListMigration( getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, getCurrenciesUseCase = getCurrenciesUseCase, + derivePublicKeysUseCase = derivePublicKeysUseCase, + addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, ) init { @@ -303,10 +309,13 @@ internal class TokensListViewModel @Inject constructor( fun onSaveButtonClick() { analyticsSender.sendWhenSaveButtonClicked() - tokensListMigration.onSaveButtonClick( - changedTokensList = changedTokensList, - changedBlockchainList = changedBlockchainList, - ) + + viewModelScope.launch(dispatchers.main) { + tokensListMigration.onSaveButtonClick( + changedTokensList = changedTokensList, + changedBlockchainList = changedBlockchainList, + ) + } } private fun onSearchValueChange(newValue: String) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt deleted file mode 100644 index eb39d0d64b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt +++ /dev/null @@ -1,250 +0,0 @@ -package com.tangem.tap.features.tokens.legacy.redux - -import com.tangem.blockchain.blockchains.cardano.CardanoUtils -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.CompletionResult -import com.tangem.common.card.EllipticCurve -import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.extensions.toMapKey -import com.tangem.core.navigation.NavigationAction -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.common.configs.CardConfig -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.common.util.supportsHdWallet -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.tokens.TokensAction -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager -import kotlinx.coroutines.launch -import org.rekotlin.Middleware -import timber.log.Timber - -@Suppress("LargeClass") -object TokensMiddleware { - - // TODO: Move to DI - private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) { - val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository) - val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository) - - AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository) - } - - val tokensMiddleware: Middleware = { _, _ -> - { next -> - { action -> - when (action) { - is TokensAction.SaveChanges -> handleSaveChanges(action) - } - next(action) - } - } - } - - private fun handleSaveChanges(action: TokensAction.SaveChanges) { - scope.launch { - val scanResponse = action.userWallet.scanResponse - - val currentTokens = action.currentTokens - val currentBlockchains = action.currentCoins - - val blockchainsToAdd = action.changedCoins.filterNot(currentBlockchains::contains) - val blockchainsToRemove = currentBlockchains.filterNot(action.changedCoins::contains) - - val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains) - val tokensToRemove = currentTokens.filterNot { token -> action.changedTokens.any { it == token } } - - removeCurrenciesIfNeeded( - userWalletId = action.userWallet.walletId, - currencies = blockchainsToRemove + tokensToRemove, - ) - - val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty() - val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() - if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) { - store.dispatchDebugErrorNotification(message = "Nothing to save") - store.dispatchOnMain(NavigationAction.PopBackTo()) - return@launch - } - - val currencyList = blockchainsToAdd + tokensToAdd - - val featureToggles = store.state.daggerGraphState.get(DaggerGraphState::testerFeatureToggles) - if (featureToggles.isDerivePublicKeysRefactoringEnabled) { - val derivePublicKeys = DefaultDerivePublicKeysUseCase( - tangemSdkManager = tangemSdkManager, - derivationsRepository = store.state.daggerGraphState.get(DaggerGraphState::derivationsRepository), - ) - - derivePublicKeys(userWalletId = action.userWallet.walletId, currencies = currencyList) - .onRight { - addCryptoCurrenciesUseCase( - userWalletId = action.userWallet.walletId, - currencies = currencyList, - ) - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - .onLeft { Timber.e("Failed to derive public keys: $it") } - } else { - // TODO: delete [REDACTED_JIRA] - if (scanResponse.supportsHdWallet()) { - deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) { - submitAdd( - userWallet = action.userWallet, - updatedScanResponse = it, - currencyList = currencyList, - ) - } - } else { - submitAdd(action.userWallet, scanResponse, currencyList) - } - } - } - } - - @Deprecated(message = "Use DerivePublicKeysUseCase instead") - private fun deriveMissingCoins( - scanResponse: ScanResponse, - currencyList: List, - onSuccess: (ScanResponse) -> Unit, - ) { - val config = CardConfig.createConfig(scanResponse.card) - val derivationDataList = currencyList.mapNotNull { currency -> - val curve = config.primaryCurve(blockchain = Blockchain.fromId(currency.network.id.value)) - curve?.let { getDerivations(curve, scanResponse, currency) } - } - val derivations = buildMap> { - derivationDataList.forEach { - val current = this[it.derivations.first] - if (current != null) { - current.addAll(it.derivations.second) - current.distinct() - } else { - this[it.derivations.first] = it.derivations.second.toMutableList() - } - } - } - - if (derivations.isEmpty()) { - onSuccess(scanResponse) - return - } - - scope.launch { - val result = tangemSdkManager.derivePublicKeys( - cardId = null, - derivations = derivations, - ) - when (result) { - is CompletionResult.Success -> { - val newDerivedKeys = result.data.entries - val oldDerivedKeys = scanResponse.derivedKeys - - val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() - - val updatedDerivedKeys = walletKeys.associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) - val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) - ExtendedPublicKeysMap(oldDerivations + newDerivations) - } - val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys) - - store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) - - onSuccess(updatedScanResponse) - } - is CompletionResult.Failure -> { - store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens")) - } - } - } - } - - private fun getDerivations( - curve: EllipticCurve, - scanResponse: ScanResponse, - currency: CryptoCurrency, - ): DerivationData? { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - - val blockchain = Blockchain.fromId(currency.network.id.value) - val supportedCurves = blockchain.getSupportedCurves() - val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) - .takeIf { supportedCurves.contains(curve) } - - val customPath = currency.network.derivationPath.value?.let { - DerivationPath(it) - }.takeIf { supportedCurves.contains(curve) } - - val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() - if (bothCandidates.isEmpty()) return null - - if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) { - currency.network.derivationPath.value?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } - } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys: ExtendedPublicKeysMap = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) - val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - - val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } - if (toDerive.isEmpty()) return null - - return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) - } - - class DerivationData(val derivations: Pair>) - - private fun submitAdd( - userWallet: UserWallet, - updatedScanResponse: ScanResponse, - currencyList: List, - ) { - scope.launch { - userWalletsListManager.update( - userWalletId = userWallet.walletId, - update = { it.copy(scanResponse = updatedScanResponse) }, - ).doOnSuccess { - addCryptoCurrenciesUseCase(userWallet.walletId, currencyList) - } - } - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - - private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List) { - if (currencies.isEmpty()) return - val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository) - val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade) - - currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies) - - walletManagersFacade.remove( - userWalletId = userWalletId, - networks = currencies - .filterIsInstance() - .mapTo(hashSetOf(), CryptoCurrency::network), - ) - walletManagersFacade.removeTokens( - userWalletId = userWalletId, - tokens = currencies.filterIsInstance().toSet(), - ) - } -} \ No newline at end of file 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 36123cf1a9..a3425aeb83 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 @@ -46,6 +46,11 @@ object TradeCryptoMiddleware { } } + private val isSendRedesignedEnabled: Boolean + get() = store.state.daggerGraphState.get( + getDependency = DaggerGraphState::sendFeatureToggles, + ).isRedesignedSendEnabled + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun handle(state: () -> AppState?, action: TradeCryptoAction) { if (DemoHelper.tryHandle(state, action)) return @@ -54,11 +59,21 @@ object TradeCryptoMiddleware { is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) is TradeCryptoAction.Buy -> proceedBuyAction(state, action) is TradeCryptoAction.Sell -> proceedSellAction(action) - is TradeCryptoAction.Swap -> openSwap( - currency = action.cryptoCurrency, - ) - is TradeCryptoAction.SendToken -> handleSendToken(action = action) - is TradeCryptoAction.SendCoin -> handleSendCoin(action = action) + is TradeCryptoAction.Swap -> openSwap(currency = action.cryptoCurrency) + is TradeCryptoAction.SendToken -> { + if (isSendRedesignedEnabled) { + handleNewSendToken(action = action) + } else { + handleSendToken(action = action) + } + } + is TradeCryptoAction.SendCoin -> { + if (isSendRedesignedEnabled) { + handleNewSendCoin(action = action) + } else { + handleSendCoin(action = action) + } + } } } @@ -280,4 +295,35 @@ object TradeCryptoMiddleware { store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle)) } } + + private fun handleNewSendToken(action: TradeCryptoAction.SendToken) { + handleNewSend( + userWalletId = action.userWallet.walletId.stringValue, + txInfo = action.transactionInfo, + currency = action.tokenCurrency, + ) + } + + private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) { + handleNewSend( + userWalletId = action.userWallet.walletId.stringValue, + txInfo = action.transactionInfo, + currency = action.coinStatus.currency, + ) + } + + private fun handleNewSend( + userWalletId: String, + txInfo: TradeCryptoAction.TransactionInfo?, + currency: CryptoCurrency, + ) { + val bundle = bundleOf( + SendRouter.CRYPTO_CURRENCY_KEY to currency, + SendRouter.USER_WALLET_ID_KEY to userWalletId, + SendRouter.TRANSACTION_ID_KEY to txInfo?.transactionId, + SendRouter.DESTINATION_ADDRESS_KEY to txInfo?.destinationAddress, + SendRouter.AMOUNT_KEY to txInfo?.amount, + ) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index b139b14200..c3f3587c26 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -67,6 +67,6 @@ interface ExchangeUrlBuilder { companion object { const val SCHEME = "https" - const val SUCCESS_URL = "tangem://success.tangem.com" + const val SUCCESS_URL = "https://tangem.com/success" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt index ce7c4c2a7f..e5948ce873 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt @@ -41,4 +41,11 @@ data class MoonPayCurrencies( val isSupportedInUS: Boolean = false, val isSellSupported: Boolean = false, val notAllowedUSStates: List = emptyList(), + val metadata: MoonPayCurrenciesMetadata? = null, +) + +@JsonClass(generateAdapter = true) +data class MoonPayCurrenciesMetadata( + val contractAddress: String?, + val networkCode: String?, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 664f31e958..328904eb0a 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -81,6 +81,7 @@ class MoonPayService( override fun availableForSell(currency: Currency): Boolean { val availableForSell = status?.availableForSell ?: return false + val metadata = status?.responseCurrencies?.map { it.metadata } if (!isSellAllowed()) return false return when (currency) { @@ -92,7 +93,9 @@ class MoonPayService( else -> availableForSell.contains(currency.currencySymbol) } } - is Currency.Token -> false + is Currency.Token -> { + metadata?.any { it?.contractAddress.equals(currency.token.contractAddress, ignoreCase = true) } ?: false + } } } diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt deleted file mode 100644 index 63b07213d1..0000000000 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ /dev/null @@ -1,298 +0,0 @@ -package com.tangem.tap.proxy - -import com.tangem.blockchain.blockchains.cardano.CardanoUtils -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.common.CompletionResult -import com.tangem.common.card.EllipticCurve -import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.extensions.toMapKey -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.data.tokens.utils.CryptoCurrencyFactory -import com.tangem.domain.common.BlockchainNetwork -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.configs.CardConfig -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.common.util.hasDerivation -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.lib.crypto.DerivationManager -import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.Currency.NonNativeToken -import com.tangem.lib.crypto.models.errors.UserCancelledException -import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TapError -import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware -import com.tangem.tap.scope -import com.tangem.tap.userWalletsListManager -import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlin.coroutines.suspendCoroutine -import com.tangem.tap.domain.model.Currency as WalletModelCurrency - -class DerivationManagerImpl( - private val appStateHolder: AppStateHolder, - private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, -) : DerivationManager { - - // TODO: Move to DI - private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) { - AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository) - } - - override suspend fun deriveAndAddTokens(currency: Currency) = suspendCoroutine { continuation -> - val selectedUserWallet = requireNotNull( - userWalletsListManager.selectedUserWalletSync, - ) { "selectedUserWallet shouldn't be null" } - val scanResponse = selectedUserWallet.scanResponse - val blockchain = requireNotNull( - Blockchain.fromNetworkId(currency.networkId), - ) { "unsupported blockchain" } - val derivationStyleProvider = scanResponse.derivationStyleProvider - val derivationPath = requireNotNull( - blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath, - ) { "derivationPath shouldn't be null" } - val hasDerivation = scanResponse.hasDerivation( - blockchain, - derivationPath, - ) - if (hasDerivation) { - scope.launch { - addToken( - userWalletId = selectedUserWallet.walletId, - blockchain = blockchain, - currency = currency, - derivationPath = derivationPath, - derivationStyleProvider = derivationStyleProvider, - ) - continuation.resumeWith(Result.success(derivationPath)) - } - } else { - val blockchainNetwork = BlockchainNetwork(blockchain, scanResponse.derivationStyleProvider) - val appCurrency = com.tangem.tap.domain.model.Currency.fromBlockchainNetwork( - blockchainNetwork, - getAppToken(currency), - ) - deriveMissingBlockchains( - scanResponse = scanResponse, - currencyList = listOf(appCurrency), - onSuccess = { updatedScanResponse -> - scope.launch { - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { it.copy(scanResponse = updatedScanResponse) }, - ) - addToken( - userWalletId = selectedUserWallet.walletId, - blockchain = blockchain, - currency = currency, - derivationPath = derivationPath, - derivationStyleProvider = derivationStyleProvider, - ) - continuation.resumeWith(Result.success(derivationPath)) - } - }, - onFailure = { - continuation.resumeWith(Result.failure(it)) - }, - ) - } - } - - private suspend fun addToken( - userWalletId: UserWalletId, - blockchain: Blockchain, - currency: Currency, - derivationPath: String, - derivationStyleProvider: DerivationStyleProvider, - ) { - val cryptoCurrency = convertCurrency( - blockchain = blockchain, - currency = currency, - derivationPath = derivationPath, - derivationStyleProvider = derivationStyleProvider, - ) - - addCryptoCurrenciesUseCase(userWalletId, cryptoCurrency) - } - - private fun convertCurrency( - blockchain: Blockchain, - currency: Currency, - derivationPath: String, - derivationStyleProvider: DerivationStyleProvider, - ): CryptoCurrency { - val cryptoCurrencyFactory = CryptoCurrencyFactory() - return when (currency) { - is Currency.NativeToken -> { - cryptoCurrencyFactory.createCoin( - blockchain = blockchain, - extraDerivationPath = derivationPath, - derivationStyleProvider = derivationStyleProvider, - ) - } - is NonNativeToken -> { - val sdkToken = Token( - name = currency.name, - symbol = currency.symbol, - contractAddress = currency.contractAddress, - decimals = currency.decimalCount, - id = currency.id, - ) - cryptoCurrencyFactory.createToken( - sdkToken = sdkToken, - blockchain = blockchain, - extraDerivationPath = derivationPath, - derivationStyleProvider = derivationStyleProvider, - ) - } - } as CryptoCurrency - } - - private fun deriveMissingBlockchains( - scanResponse: ScanResponse, - currencyList: List, - onSuccess: (ScanResponse) -> Unit, - onFailure: (Exception) -> Unit, - ) { - val config = CardConfig.createConfig(scanResponse.card) - val derivationDataList = currencyList.mapNotNull { currency -> - val curve = config.primaryCurve(currency.blockchain) - curve?.let { getDerivations(curve, scanResponse, currency) } - } - val derivations = buildMap> { - derivationDataList.forEach { - val current = this[it.derivations.first] - if (current != null) { - current.addAll(it.derivations.second) - current.distinct() - } else { - this[it.derivations.first] = it.derivations.second.toMutableList() - } - } - } - if (derivations.isEmpty()) { - onSuccess(scanResponse) - return - } - - scope.launch { - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - - val result = appStateHolder.tangemSdkManager?.derivePublicKeys( - cardId = null, // always ignore cardId in derive task - derivations = derivations, - ) - when (result) { - is CompletionResult.Success -> { - val newDerivedKeys = result.data.entries - val oldDerivedKeys = scanResponse.derivedKeys - - val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() - - val updatedDerivedKeys = walletKeys.associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) - val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) - ExtendedPublicKeysMap(oldDerivations + newDerivations) - } - val updatedScanResponse = scanResponse.copy( - derivedKeys = updatedDerivedKeys, - ) - if (selectedUserWallet != null) { - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { it.copy(scanResponse = updatedScanResponse) }, - ) - } - appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(updatedScanResponse) - } - is CompletionResult.Failure -> { - appStateHolder.mainStore?.dispatchDebugErrorNotification( - TapError.CustomError( - "Error derivation", - ), - ) - onFailure.invoke(handleTangemError(result.error)) - } - else -> { - error("result result is null") - } - } - } - } - - private fun getDerivations( - curve: EllipticCurve, - scanResponse: ScanResponse, - currency: com.tangem.tap.domain.model.Currency, - ): TokensMiddleware.DerivationData? { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - - val supportedCurves = currency.blockchain.getSupportedCurves() - val path = currency.blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) - .takeIf { supportedCurves.contains(curve) } - - val customPath = currency.derivationPath?.let { - DerivationPath(it) - }.takeIf { supportedCurves.contains(curve) } - - val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() - if (bothCandidates.isEmpty()) return null - - if (currency is WalletModelCurrency.Blockchain && - currency.blockchain == Blockchain.Cardano - ) { - currency.derivationPath?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } - } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys: ExtendedPublicKeysMap = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) - val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - - val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } - if (toDerive.isEmpty()) return null - - return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) - } - - private fun getAppToken(currency: Currency): Token? { - return if (currency is NonNativeToken) { - Token( - symbol = currency.symbol, - contractAddress = currency.contractAddress, - decimals = currency.decimalCount, - ) - } else { - null - } - } - /** - * Simple error handler - * for now specifically handle only UserCancelled - * - * @param error [TangemError] - */ - private fun handleTangemError(error: TangemError): Exception { - if (error is TangemSdkError.UserCancelled) { - return UserCancelledException() - } - return IllegalStateException(error.customMessage) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 8c0c4eda47..d72bae0238 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -4,9 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.* @@ -56,18 +54,4 @@ internal object ProxyModule { walletManagersFacade = walletManagersFacade, ) } - - @Provides - @Singleton - fun provideDerivationManager( - appStateHolder: AppStateHolder, - currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, - ): DerivationManager { - return DerivationManagerImpl( - appStateHolder = appStateHolder, - currenciesRepository = currenciesRepository, - networksRepository = networksRepository, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 9dab1b838d..995658a42c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -3,7 +3,7 @@ package com.tangem.tap.proxy.redux import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.features.managetokens.navigation.ManageTokensRouter +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -19,7 +19,7 @@ sealed interface DaggerGraphAction : Action { val walletRouter: WalletRouter, val walletConnectInteractor: WalletConnectInteractor, val tokenDetailsRouter: TokenDetailsRouter, - val manageTokensRouter: ManageTokensRouter, + val manageTokensUi: ManageTokensUi, val cardSdkConfigRepository: CardSdkConfigRepository, val sendRouter: SendRouter, val qrScanningRouter: QrScanningRouter, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 5f2030f390..7074523df0 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -18,7 +18,7 @@ object DaggerGraphReducer { walletRouter = action.walletRouter, walletConnectInteractor = action.walletConnectInteractor, tokenDetailsRouter = action.tokenDetailsRouter, - manageTokensRouter = action.manageTokensRouter, + manageTokensUi = action.manageTokensUi, cardSdkConfigRepository = action.cardSdkConfigRepository, sendRouter = action.sendRouter, qrScanningRouter = action.qrScanningRouter, 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 b1839fae93..4a543195d6 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 @@ -1,5 +1,7 @@ package com.tangem.tap.proxy.redux +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -7,17 +9,15 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles -import com.tangem.features.managetokens.navigation.ManageTokensRouter +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.tester.api.TesterFeatureToggles import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter @@ -39,7 +39,7 @@ data class DaggerGraphState( val walletConnectInteractor: WalletConnectInteractor? = null, val tokenDetailsRouter: TokenDetailsRouter? = null, val manageTokensFeatureToggles: ManageTokensFeatureToggles? = null, - val manageTokensRouter: ManageTokensRouter? = null, + val manageTokensUi: ManageTokensUi? = null, val scanCardProcessor: ScanCardProcessor? = null, val cardSdkConfigRepository: CardSdkConfigRepository? = null, val appCurrencyRepository: AppCurrencyRepository? = null, @@ -52,11 +52,9 @@ data class DaggerGraphState( val sendFeatureToggles: SendFeatureToggles? = null, val sendRouter: SendRouter? = null, val qrScanningRouter: QrScanningRouter? = null, - - // FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList val currenciesRepository: CurrenciesRepository? = null, - val derivationsRepository: DerivationsRepository? = null, - val testerFeatureToggles: TesterFeatureToggles? = null, + val blockchainDataStorage: BlockchainDataStorage? = null, + val accountCreator: AccountCreator? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/app/src/main/res/layout/layout_send_address.xml b/app/src/main/res/layout/layout_send_address.xml index 8367bf3524..1256f33037 100644 --- a/app/src/main/res/layout/layout_send_address.xml +++ b/app/src/main/res/layout/layout_send_address.xml @@ -260,6 +260,70 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 830d2621c2..a684154112 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -125,6 +125,7 @@ sealed class AnalyticsParam { const val SOURCE = "Source" const val BALANCE = "Balance" const val BATCH = "Batch" + const val TYPE = "Type" const val FEE_TYPE = "Fee Type" const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 137ccdb922..522cd3257e 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -33,7 +33,6 @@ dependencies { implementation(deps.timber) /** Network */ - implementation(deps.krateSharedPref) implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.okHttp) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt index 568b042179..e81aa15e06 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/BigDecimalAdapter.kt @@ -7,7 +7,7 @@ import java.math.BigDecimal /** [REDACTED_AUTHOR] */ -class BigDecimalAdapter { +internal class BigDecimalAdapter { @FromJson fun fromJson(value: String) = BigDecimal(value) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/DateTimeAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/DateTimeAdapter.kt new file mode 100644 index 0000000000..38f4b81532 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/DateTimeAdapter.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.common + +import com.squareup.moshi.* +import org.joda.time.DateTime + +internal class DateTimeAdapter : JsonAdapter() { + + @FromJson + override fun fromJson(reader: JsonReader): DateTime? { + val dateString = reader.nextString() ?: return null + + return DateTime.parse(dateString) + } + + @ToJson + override fun toJson(writer: JsonWriter, value: DateTime?) { + if (value != null) { + writer.value(value.toString()) + } else { + writer.nullValue() + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/LocalDateAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/LocalDateAdapter.kt index b11c76fccd..95978206f2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/LocalDateAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/LocalDateAdapter.kt @@ -1,14 +1,10 @@ package com.tangem.datasource.api.common -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson +import com.squareup.moshi.* import org.joda.time.LocalDate import org.joda.time.format.DateTimeFormat -class LocalDateAdapter : JsonAdapter() { +internal class LocalDateAdapter : JsonAdapter() { private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallAdapterFactory.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallAdapterFactory.kt index 11eca2e0d6..8f8c65e92d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallAdapterFactory.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallAdapterFactory.kt @@ -6,7 +6,7 @@ import retrofit2.Retrofit import java.lang.reflect.ParameterizedType import java.lang.reflect.Type -internal class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() { +class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() { override fun get(returnType: Type, annotations: Array, retrofit: Retrofit): CallAdapter<*, *>? { if (getRawType(returnType) != Call::class.java) { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 43ff3af6e5..63e632438e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -75,4 +75,11 @@ interface TangemTechApi { @GET("promotion") suspend fun getPromotionInfo(@Query("programName") name: String): ApiResponse + + @POST("user-network-account") + suspend fun createUserNetworkAccount( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Body body: CreateUserNetworkAccountBody, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountBody.kt new file mode 100644 index 0000000000..f359bbd961 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountBody.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class CreateUserNetworkAccountBody( + @Json(name = "networkId") val networkId: String, + @Json(name = "walletPublicKey") val publicWalletKey: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountResponse.kt new file mode 100644 index 0000000000..2dcb8bfb8f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserNetworkAccountResponse.kt @@ -0,0 +1,17 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class CreateUserNetworkAccountResponse( + @Json(name = "status") val status: Boolean, + @Json(name = "data") val data: AccountCreated, +) { + + @JsonClass(generateAdapter = true) + data class AccountCreated( + @Json(name = "accountId") val accountId: String, + @Json(name = "publicWalletKey") val publicWalletKey: String, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index ed2d89b8d2..580334eb2b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.config import com.tangem.blockchain.common.* +import com.tangem.datasource.BuildConfig import com.tangem.datasource.config.ConfigManager.Companion.IS_CREATING_TWIN_CARDS_ALLOWED import com.tangem.datasource.config.ConfigManager.Companion.IS_TOP_UP_ENABLED import com.tangem.datasource.config.models.Config @@ -105,7 +106,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { sprinklr = configValues.sprinklr, walletConnectProjectId = configValues.walletConnectProjectId, tangemComAuthorization = configValues.tangemComAuthorization, - express = configValues.express, + express = if (BuildConfig.ENVIRONMENT == "dev") configValues.devExpress else configValues.express, ) } @@ -145,6 +146,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { jsonRpc = accessTokens.bitcoin?.jsonRPC, blockBookRest = accessTokens.bitcoin?.blockBookRest, ), + algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index ec2d4845ad..eaedb32084 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -40,6 +40,7 @@ class ConfigValueModel( val tangemComAuthorization: String?, val chiaFireAcademyApiKey: String?, val chiaTangemApiKey: String?, + val devExpress: ExpressModel?, val express: ExpressModel?, ) @@ -66,6 +67,7 @@ data class GetBlockAccessTokens( @Json(name = "litecoin") val litecoin: GetBlockToken?, @Json(name = "dash") val dash: GetBlockToken?, @Json(name = "bitcoin") val bitcoin: GetBlockToken?, + @Json(name = "algorand") val algorand: GetBlockToken?, ) @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt new file mode 100644 index 0000000000..7abd08cf54 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt @@ -0,0 +1,22 @@ +package com.tangem.datasource.di + +import com.tangem.blockchain.common.AccountCreator +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.local.blockchain.DefaultAccountCreator +import com.tangem.lib.auth.AuthProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object AccountCreatorModule { + + @Provides + @Singleton + fun provideAccountCreator(authProvider: AuthProvider, @DevTangemApi tangemTechApi: TangemTechApi): AccountCreator { + return DefaultAccountCreator(authProvider, tangemTechApi) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AppPreferenceStorageModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AppPreferenceStorageModule.kt deleted file mode 100644 index 2eefec13ed..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AppPreferenceStorageModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.datasource.di - -import com.tangem.datasource.local.AppPreferenceStorage -import com.tangem.datasource.local.AppPreferenceStorageImpl -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface AppPreferenceStorageModule { - - @Binds - @Singleton - fun bindAppPreferenceStorage(appPreferenceStorageImpl: AppPreferenceStorageImpl): AppPreferenceStorage -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt new file mode 100644 index 0000000000..3d285b3fbe --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.di + +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.datasource.local.blockchain.DefaultBlockchainDataStorage +import com.tangem.datasource.local.preferences.AppPreferencesStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object BlockchainDataStorageModule { + + @Provides + @Singleton + fun provideBlockchainDataStorage(appPreferencesStore: AppPreferencesStore): BlockchainDataStorage { + return DefaultBlockchainDataStorage(appPreferencesStore) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt index bf3d94095f..c22d4433bf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt @@ -4,6 +4,7 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.common.json.MoshiJsonConverter import com.tangem.datasource.api.common.BigDecimalAdapter +import com.tangem.datasource.api.common.DateTimeAdapter import com.tangem.datasource.api.common.LocalDateAdapter import dagger.Module import dagger.Provides @@ -22,6 +23,7 @@ class MoshiModule { return Moshi.Builder() .add(BigDecimalAdapter()) .add(LocalDateAdapter()) + .add(DateTimeAdapter()) .add(KotlinJsonAdapterFactory()) .build() } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorage.kt deleted file mode 100644 index d56ca29fa0..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorage.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.datasource.local - -/** - * Application local storage - * -[REDACTED_AUTHOR] - */ -@Deprecated(message = "Use AppPreferencesStore", level = DeprecationLevel.WARNING) -interface AppPreferenceStorage { - - /** Json config with feature toggles 'ToggleName: String - Availability: Boolean' */ - var featureToggles: String -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorageImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorageImpl.kt deleted file mode 100644 index 3ce9a8aca9..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/AppPreferenceStorageImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.datasource.local - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import hu.autsoft.krate.SimpleKrate -import hu.autsoft.krate.default.withDefault -import hu.autsoft.krate.stringPref -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Implementation of application local storage - * - * @param context application context - * -[REDACTED_AUTHOR] - */ -@Singleton -internal class AppPreferenceStorageImpl @Inject constructor( - @ApplicationContext context: Context, -) : SimpleKrate(context = context), AppPreferenceStorage { - - override var featureToggles: String by stringPref().withDefault("") -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt new file mode 100644 index 0000000000..1501279d83 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt @@ -0,0 +1,31 @@ +package com.tangem.datasource.local.blockchain + +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.extensions.Result +import com.tangem.common.extensions.toHexString +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CreateUserNetworkAccountBody +import com.tangem.lib.auth.AuthProvider + +internal class DefaultAccountCreator( + private val authProvider: AuthProvider, + private val tangemTechApi: TangemTechApi, +) : AccountCreator { + + override suspend fun createAccount(blockchain: Blockchain, walletPublicKey: ByteArray): Result { + val request = CreateUserNetworkAccountBody(blockchain.id.removeSuffix("/test"), walletPublicKey.toHexString()) + return try { + val response = tangemTechApi.createUserNetworkAccount( + cardPublicKey = authProvider.getCardPublicKey(), + cardId = authProvider.getCardId(), + body = request, + ).getOrThrow() + Result.Success(response.data.accountId) + } catch (e: Exception) { + Result.Failure(BlockchainSdkError.FailedToCreateAccount) + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt new file mode 100644 index 0000000000..3f3d7192f5 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt @@ -0,0 +1,29 @@ +package com.tangem.datasource.local.blockchain + +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.utils.getSyncOrNull + +/** + * [BlockchainDataStorage] implementation + * + * @property appPreferencesStore app preferences store + * +[REDACTED_AUTHOR] + */ +internal class DefaultBlockchainDataStorage( + private val appPreferencesStore: AppPreferencesStore, +) : BlockchainDataStorage { + + override suspend fun getOrNull(key: String): String? { + return appPreferencesStore.getSyncOrNull(key = stringPreferencesKey(name = key)) + } + + override suspend fun store(key: String, value: String) { + appPreferencesStore.edit { + it[stringPreferencesKey(key)] = value + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 652187147e..c5c54133ac 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -52,6 +52,8 @@ object PreferencesKeys { val IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY by lazy { booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown") } + + val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/DefaultTxHistoryItemsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/DefaultTxHistoryItemsStore.kt index 7cbeb3f317..c992faf5bd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/DefaultTxHistoryItemsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/DefaultTxHistoryItemsStore.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.local.txhistory import com.tangem.datasource.local.datastore.core.StringKeyDataStore import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator +import com.tangem.domain.txhistory.models.Page import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.utils.extensions.addOrReplace @@ -13,28 +14,16 @@ internal class DefaultTxHistoryItemsStore( override fun provideStringKey(key: TxHistoryItemsStore.Key): String = key.toString() - override suspend fun getNextPageSyncOrNull(key: TxHistoryItemsStore.Key): Int? { - val storedValue = getSyncOrNull(key) ?: return null - val lastWrappedItems = storedValue.maxBy(PaginationWrapper<*>::page) - val lastPage = lastWrappedItems.page - - return if (lastPage <= lastWrappedItems.totalPages) { - lastPage - } else { - null - } - } - - override suspend fun getSyncOrNull(key: TxHistoryItemsStore.Key, page: Int): PaginationWrapper? { + override suspend fun getSyncOrNull(key: TxHistoryItemsStore.Key, page: Page): PaginationWrapper? { val storedValue = getSyncOrNull(key) - return storedValue?.firstOrNull { it.page == page } + return storedValue?.firstOrNull { it.currentPage == page } } override suspend fun store(key: TxHistoryItemsStore.Key, value: PaginationWrapper) { val oldValue = getSyncOrNull(key).orEmpty() val newValue = oldValue.addOrReplace(value) { - it.page == value.page + it.currentPage == value.currentPage } store(key, newValue) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/TxHistoryItemsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/TxHistoryItemsStore.kt index e2f288e235..b49bc4fdd4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/TxHistoryItemsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/TxHistoryItemsStore.kt @@ -1,15 +1,14 @@ package com.tangem.datasource.local.txhistory import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.txhistory.models.Page import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.wallets.models.UserWalletId interface TxHistoryItemsStore { - suspend fun getNextPageSyncOrNull(key: Key): Int? - - suspend fun getSyncOrNull(key: Key, page: Int): PaginationWrapper? + suspend fun getSyncOrNull(key: Key, page: Page): PaginationWrapper? suspend fun remove(key: Key) diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt index 286f8f8c73..3c31ec55a7 100644 --- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt @@ -4,7 +4,7 @@ import com.tangem.core.deeplink.DeepLink class BuyCurrencyDeepLink(val onReceive: () -> Unit) : DeepLink { - override val uri: String = "tangem://success.tangem.com" + override val uri: String = "tangem://redirect?action=dismissBrowser" override fun onReceive(params: Map) { onReceive() diff --git a/core/featuretoggles/build.gradle.kts b/core/featuretoggles/build.gradle.kts index c5f9b07545..5354b55813 100644 --- a/core/featuretoggles/build.gradle.kts +++ b/core/featuretoggles/build.gradle.kts @@ -7,14 +7,22 @@ plugins { } dependencies { - implementation(project(":core:datasource")) + /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) + + /** Local storages */ + implementation(deps.androidx.datastore) + + /** Other libraries */ implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) + /** Core modules */ + implementation(projects.core.datasource) + testImplementation(deps.test.coroutine) testImplementation(deps.test.junit) testImplementation(deps.test.mockk) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index e91e7f03e8..e76218c369 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -14,13 +14,5 @@ { "name": "REDESIGNED_SEND_SCREEN_ENABLED", "version": "undefined" - }, - { - "name": "WALLETS_SCROLLING_PREVIEW_ENABLED", - "version": "5.5.0" - }, - { - "name": "DERIVE_PUBLIC_KEYS_REFACTORING_ENABLED", - "version": "5.5.0" } ] diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt index eadb4bf0fb..e67ca2a738 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt @@ -11,7 +11,7 @@ import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage import com.tangem.core.featuretoggle.version.DefaultVersionProvider import com.tangem.core.featuretoggles.BuildConfig import com.tangem.datasource.asset.AssetReader -import com.tangem.datasource.local.AppPreferenceStorage +import com.tangem.datasource.local.preferences.AppPreferencesStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -29,7 +29,7 @@ internal object FeatureTogglesManagerModule { fun provideFeatureTogglesManager( @ApplicationContext context: Context, assetReader: AssetReader, - appPreferenceStorage: AppPreferenceStorage, + appPreferencesStore: AppPreferencesStore, ): FeatureTogglesManager { val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val localFeatureTogglesStorage = LocalFeatureTogglesStorage( @@ -41,8 +41,7 @@ internal object FeatureTogglesManagerModule { return if (BuildConfig.TESTER_MENU_ENABLED) { DevFeatureTogglesManager( localFeatureTogglesStorage = localFeatureTogglesStorage, - appPreferenceStorage = appPreferenceStorage, - jsonAdapter = moshi.adapter(), + appPreferencesStore = appPreferencesStore, versionProvider = versionProvider, ) } else { diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt index 12e0a496b6..9b29a65f66 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt @@ -1,25 +1,25 @@ package com.tangem.core.featuretoggle.manager import androidx.annotation.VisibleForTesting -import com.squareup.moshi.JsonAdapter import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage import com.tangem.core.featuretoggle.utils.associateToggles import com.tangem.core.featuretoggle.version.VersionProvider -import com.tangem.datasource.local.AppPreferenceStorage +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject import kotlin.properties.Delegates /** * Feature toggles manager implementation in DEV build * * @property localFeatureTogglesStorage local feature toggles storage - * @property appPreferenceStorage application local storage - * @property jsonAdapter adapter for parsing json + * @property appPreferencesStore application local store * @property versionProvider application version provider */ internal class DevFeatureTogglesManager( private val localFeatureTogglesStorage: FeatureTogglesStorage, - private val appPreferenceStorage: AppPreferenceStorage, - private val jsonAdapter: JsonAdapter>, + private val appPreferencesStore: AppPreferencesStore, private val versionProvider: VersionProvider, ) : MutableFeatureTogglesManager { @@ -28,11 +28,9 @@ internal class DevFeatureTogglesManager( override suspend fun init() { localFeatureTogglesStorage.init() - val savedFeatureToggles = if (appPreferenceStorage.featureToggles.isNotEmpty()) { - jsonAdapter.fromJson(appPreferenceStorage.featureToggles).orEmpty() - } else { - emptyMap() - } + val savedFeatureToggles = appPreferencesStore.getObjectSyncOrNull>( + key = PreferencesKeys.FEATURE_TOGGLES_KEY, + ) ?: emptyMap() featureTogglesMap = localFeatureTogglesStorage.featureToggles .associateToggles(currentVersion = versionProvider.get().orEmpty()) @@ -46,10 +44,10 @@ internal class DevFeatureTogglesManager( override fun getFeatureToggles(): Map = featureTogglesMap - override fun changeToggle(name: String, isEnabled: Boolean) { + override suspend fun changeToggle(name: String, isEnabled: Boolean) { featureTogglesMap[name] ?: return featureTogglesMap[name] = isEnabled - appPreferenceStorage.featureToggles = jsonAdapter.toJson(featureTogglesMap) + appPreferencesStore.storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, featureTogglesMap) } @VisibleForTesting(otherwise = VisibleForTesting.NONE) diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt index abf6ff5b72..8d7a063c5d 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt @@ -11,5 +11,5 @@ interface MutableFeatureTogglesManager : FeatureTogglesManager { fun getFeatureToggles(): Map /** Change availability [isEnabled] of toggle with name [name] */ - fun changeToggle(name: String, isEnabled: Boolean) + suspend fun changeToggle(name: String, isEnabled: Boolean) } \ No newline at end of file diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/FeatureToggle.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/FeatureToggle.kt index 69de7b76e7..b019bfc810 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/FeatureToggle.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/FeatureToggle.kt @@ -1,5 +1,8 @@ package com.tangem.core.featuretoggle.storage +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + /** * Data model with information about feature toggle * @@ -10,4 +13,8 @@ package com.tangem.core.featuretoggle.storage * [REDACTED_AUTHOR] */ -internal data class FeatureToggle(val name: String, val version: String) \ No newline at end of file +@JsonClass(generateAdapter = true) +internal data class FeatureToggle( + @Json(name = "name") val name: String, + @Json(name = "version") val version: String, +) \ No newline at end of file diff --git a/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManagerTest.kt b/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManagerTest.kt index 51ac267ee2..e0e8fbef2f 100644 --- a/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManagerTest.kt +++ b/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManagerTest.kt @@ -2,48 +2,42 @@ package com.tangem.core.featuretoggle.manager import android.annotation.SuppressLint import com.google.common.truth.Truth -import com.squareup.moshi.JsonAdapter import com.tangem.core.featuretoggle.storage.FeatureToggle import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage import com.tangem.core.featuretoggle.utils.associateToggles import com.tangem.core.featuretoggle.version.VersionProvider -import com.tangem.datasource.local.AppPreferenceStorage -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerifyOrder -import io.mockk.just -import io.mockk.mockk -import io.mockk.verifyAll -import io.mockk.verifyOrder -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.getSyncOrNull +import io.mockk.* import kotlinx.coroutines.test.runTest import org.junit.Test +import kotlin.collections.set /** [REDACTED_AUTHOR] */ -@OptIn(ExperimentalCoroutinesApi::class) @SuppressLint("CheckResult") internal class DevFeatureTogglesManagerTest { private val localFeatureTogglesStorage = mockk() - private val appPreferenceStorage = mockk(relaxed = true) - private val jsonAdapter = mockk>>() + private val appPreferenceStore = mockk(relaxed = true) private val versionProvider = mockk() private val manager = DevFeatureTogglesManager( localFeatureTogglesStorage = localFeatureTogglesStorage, - appPreferenceStorage = appPreferenceStorage, - jsonAdapter = jsonAdapter, + appPreferencesStore = appPreferenceStore, versionProvider = versionProvider, ) @Test fun `successfully initialize storage if shared prefs kept feature toggles`() = runTest { - val currentVersion = "1.0.0" + val currentVersion = "0.1.0" coEvery { localFeatureTogglesStorage.init() } just Runs - coEvery { appPreferenceStorage.featureToggles } returns savedFeatureToggles - coEvery { jsonAdapter.fromJson(savedFeatureToggles) } returns savedFeatureTogglesMap + coEvery { + appPreferenceStore.getObjectSyncOrNull>(PreferencesKeys.FEATURE_TOGGLES_KEY) + } returns savedFeatureTogglesMap coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles coEvery { versionProvider.get() } returns currentVersion @@ -51,7 +45,6 @@ internal class DevFeatureTogglesManagerTest { coVerifyOrder { localFeatureTogglesStorage.init() - jsonAdapter.fromJson(savedFeatureToggles) versionProvider.get() } @@ -66,12 +59,12 @@ internal class DevFeatureTogglesManagerTest { @Test fun `successfully initialize storage if shared prefs kept empty list`() = runTest { - val currentVersion = "1.0.0" - val sharedPrefFeatureToggles = "[]" + val currentVersion = "0.1.0" coEvery { localFeatureTogglesStorage.init() } just Runs - coEvery { appPreferenceStorage.featureToggles } returns sharedPrefFeatureToggles - coEvery { jsonAdapter.fromJson(sharedPrefFeatureToggles) } returns savedFeatureTogglesMap + coEvery { + appPreferenceStore.getObjectSyncOrNull>(PreferencesKeys.FEATURE_TOGGLES_KEY) + } returns emptyMap() coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles coEvery { versionProvider.get() } returns currentVersion @@ -79,7 +72,6 @@ internal class DevFeatureTogglesManagerTest { coVerifyOrder { localFeatureTogglesStorage.init() - jsonAdapter.fromJson(sharedPrefFeatureToggles) versionProvider.get() } @@ -94,10 +86,12 @@ internal class DevFeatureTogglesManagerTest { @Test fun `successfully initialize storage if shared prefs didn't keep feature toggles`() = runTest { - val currentVersion = "1.0.0" + val currentVersion = "0.1.0" coEvery { localFeatureTogglesStorage.init() } just Runs - coEvery { appPreferenceStorage.featureToggles } returns "" + coEvery { + appPreferenceStore.getObjectSyncOrNull>(PreferencesKeys.FEATURE_TOGGLES_KEY) + } returns null coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles coEvery { versionProvider.get() } returns currentVersion @@ -107,7 +101,6 @@ internal class DevFeatureTogglesManagerTest { localFeatureTogglesStorage.init() versionProvider.get() } - verifyAll(inverse = true) { jsonAdapter.fromJson(any()) } val expected = localFeatureToggles .associateToggles(currentVersion) @@ -119,8 +112,7 @@ internal class DevFeatureTogglesManagerTest { @Test fun `successfully initialize storage if versionProvider returns null`() = runTest { coEvery { localFeatureTogglesStorage.init() } just Runs - coEvery { appPreferenceStorage.featureToggles } returns savedFeatureToggles - coEvery { jsonAdapter.fromJson(savedFeatureToggles) } returns savedFeatureTogglesMap + coEvery { appPreferenceStore.getSyncOrNull(PreferencesKeys.FEATURE_TOGGLES_KEY) } returns savedFeatureToggles coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles coEvery { versionProvider.get() } returns null @@ -128,7 +120,6 @@ internal class DevFeatureTogglesManagerTest { coVerifyOrder { localFeatureTogglesStorage.init() - jsonAdapter.fromJson(savedFeatureToggles) versionProvider.get() } @@ -180,7 +171,7 @@ internal class DevFeatureTogglesManagerTest { } @Test - fun `change toggle that contains in map`() { + fun `change toggle that contains in map`() = runTest { val changeableToggleName = "INACTIVE_TEST_FEATURE_ENABLED" val resultMap = mutableMapOf( changeableToggleName to false, @@ -188,18 +179,16 @@ internal class DevFeatureTogglesManagerTest { ) manager.setFeatureToggles(resultMap) - coEvery { jsonAdapter.toJson(resultMap) } returns "" manager.changeToggle(changeableToggleName, true) resultMap[changeableToggleName] = true - verifyOrder { jsonAdapter.toJson(resultMap) } Truth.assertThat(manager.getFeatureToggles()).containsExactlyEntriesIn(resultMap) } @Test - fun `change toggle that doesn't contains in map`() { + fun `change toggle that doesn't contains in map`() = runTest { val resultMap = mutableMapOf( "INACTIVE_TEST_FEATURE_ENABLED" to false, "ACTIVE2_TEST_FEATURE_ENABLED" to false, @@ -209,8 +198,6 @@ internal class DevFeatureTogglesManagerTest { manager.changeToggle("FEATURE_TOGGLE", true) - verifyAll(inverse = true) { jsonAdapter.toJson(any()) } - Truth.assertThat(manager.getFeatureToggles()).containsExactlyEntriesIn(resultMap) } diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 0276e3c034..ffb87108d1 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -91,7 +91,8 @@ Посмотреть историю транзакций Обозреватель Комиссия - Сетевые комиссии за транзакции используются для поддержки безопасности сети, поощрения валидаторов, выделения ресурсов и определения приоритета транзакции. + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузка на сеть, объема транзакции и приоритета исполнения. %s + Подробнее Свое Быстро По рынку @@ -449,6 +450,7 @@ Сканировать Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Приготовьте свою карту + Уже содержится в введенном адресе Сумма Вычесть из суммы отправки Сумма к получению %s @@ -535,6 +537,9 @@ Дать разрешение Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. Недостаточно средств + Комиссии + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя + В сумму включена комиссия провайдера сервиса. Недостаточно средств для оплаты комиссии на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек. Транзакция в процессе… Подождите diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 43914b654e..052973065a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -89,7 +89,8 @@ Explore transaction history Explorer Fee - Network transaction fees are used to support network security, incentivize validators, allocate resources, and determine transaction priority. + Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s + Read more Custom Fast Market @@ -285,7 +286,6 @@ Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. Not original or primary blockchain the token is hosted Non-native networks - Blockchain the cryptocurrency was initially created Networks Choose networks Wallet @@ -448,6 +448,7 @@ Scan Card Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Get your card ready! + Already included in the entered address Amount Subtract from send amount The recipient will receive %s @@ -549,6 +550,9 @@ Give Permission Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. Insufficient funds + Fees + The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes the service provider\'s fee. Insufficient funds in your %1$s wallet to cover fees. Top up your %2$s wallet first. Transaction in progress... Waiting diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index ab9b42cd1f..62f9551fa1 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -30,6 +30,8 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.ui.utils) implementation(deps.compose.coil) + implementation(deps.compose.navigation) + implementation(deps.compose.navigation.hilt) /** Other libraries */ implementation(deps.compose.accompanist.systemUiController) @@ -38,4 +40,5 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.zxing.qrCore) implementation(deps.jodatime) + implementation(deps.timber) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index ad4fc53b0a..99230c1853 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -39,6 +39,11 @@ fun SpacerH16(modifier: Modifier = Modifier) { SpacerH(16.dp, modifier) } +@Composable +fun SpacerH18(modifier: Modifier = Modifier) { + SpacerH(18.dp, modifier) +} + @Composable fun SpacerH24(modifier: Modifier = Modifier) { SpacerH(24.dp, modifier) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt index cbb97ce8e1..f3d155f5a2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt @@ -1,17 +1,13 @@ package com.tangem.core.ui.components.atoms import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import com.tangem.core.ui.res.TangemTheme /** @@ -21,7 +17,7 @@ import com.tangem.core.ui.res.TangemTheme fun Hand(modifier: Modifier = Modifier) { Box( modifier = modifier - .padding(vertical = TangemTheme.dimens.spacing8) + .height(handComposableComponentHeight) .fillMaxWidth(), contentAlignment = Alignment.Center, ) { @@ -37,6 +33,11 @@ fun Hand(modifier: Modifier = Modifier) { } } +val handComposableComponentHeight: Dp + @Composable + @ReadOnlyComposable + get() = TangemTheme.dimens.size4 + TangemTheme.dimens.spacing16 + // region Preview @Composable private fun HandSample(modifier: Modifier = Modifier) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt index 409e1430d0..093d9cfce7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt @@ -6,4 +6,6 @@ import androidx.compose.runtime.Immutable * General interface for bottom sheet config model */ @Immutable -interface TangemBottomSheetConfigContent \ No newline at end of file +interface TangemBottomSheetConfigContent { + object Empty : TangemBottomSheetConfigContent +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index 3a5bbafce5..a6c3ee8402 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -39,47 +39,57 @@ fun HorizontalActionChips( } } -@Preview +// region Preview +@Preview(widthDp = 360) @Composable private fun Preview_HorizontalActionChips_Light( - @PreviewParameter(ActionButtonConfigProvider::class) buttons: ImmutableList, + @PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChips, ) { TangemTheme(isDark = false) { - HorizontalActionChips(buttons = buttons) + HorizontalActionChips(buttons = buttons.buttons) } } -@Preview +@Preview(widthDp = 360) @Composable private fun Preview_HorizontalActionChips_Dark( - @PreviewParameter(ActionButtonConfigProvider::class) buttons: ImmutableList, + @PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChips, ) { TangemTheme(isDark = true) { - HorizontalActionChips(buttons = buttons) + HorizontalActionChips(buttons = buttons.buttons) } } -private class ActionButtonConfigProvider : CollectionPreviewParameterProvider( - collection = persistentListOf( - ActionButtonConfig( - text = TextReference.Str(value = "Buy"), - iconResId = R.drawable.ic_plus_24, - onClick = {}, - ), - ActionButtonConfig( - text = TextReference.Str(value = "Send"), - iconResId = R.drawable.ic_arrow_up_24, - onClick = {}, - ), - ActionButtonConfig( - text = TextReference.Str(value = "Receive"), - iconResId = R.drawable.ic_arrow_down_24, - onClick = {}, - ), - ActionButtonConfig( - text = TextReference.Str(value = "Exchange"), - iconResId = R.drawable.ic_exchange_vertical_24, - onClick = {}, +private class ActionButtonConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + HorizontalActionChips( + buttons = persistentListOf( + ActionButtonConfig( + text = TextReference.Str(value = "Buy"), + iconResId = R.drawable.ic_plus_24, + onClick = {}, + ), + ActionButtonConfig( + text = TextReference.Str(value = "Send"), + iconResId = R.drawable.ic_arrow_up_24, + onClick = {}, + ), + ActionButtonConfig( + text = TextReference.Str(value = "Receive"), + iconResId = R.drawable.ic_arrow_down_24, + onClick = {}, + ), + ActionButtonConfig( + text = TextReference.Str(value = "Exchange"), + iconResId = R.drawable.ic_exchange_vertical_24, + onClick = {}, + ), + ), ), ), -) \ No newline at end of file +) + +private data class HorizontalActionChips( + val buttons: ImmutableList, +) +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt index 5528c5dab9..60bc4a690f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt @@ -1,11 +1,13 @@ package com.tangem.core.ui.components.buttons +import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -51,12 +53,22 @@ fun SecondarySmallButton(config: SmallButtonConfig, modifier: Modifier = Modifie @Composable private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: Modifier = Modifier) { val shape = RoundedCornerShape(size = TangemTheme.dimens.radius16) + + val backgroundColor by animateColorAsState( + targetValue = if (isPrimary) TangemTheme.colors.button.primary else TangemTheme.colors.button.secondary, + label = "Update background color", + ) + + val textColor by animateColorAsState( + targetValue = if (isPrimary) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1, + label = "Update text color", + ) Box( modifier = modifier .defaultMinSize(minWidth = TangemTheme.dimens.size46, minHeight = TangemTheme.dimens.size24) .clip(shape) .background( - color = if (isPrimary) TangemTheme.colors.button.primary else TangemTheme.colors.button.secondary, + color = backgroundColor, shape = shape, ) .clickable(enabled = true, onClick = config.onClick) @@ -66,8 +78,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: contentAlignment = Alignment.Center, ) { Text( + modifier = Modifier.padding( + horizontal = TangemTheme.dimens.spacing10, + ), text = config.text.resolveReference(), - color = if (isPrimary) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1, + color = textColor, maxLines = 1, style = TangemTheme.typography.button, ) @@ -97,7 +112,7 @@ private fun ButtonsSample() { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { val config = SmallButtonConfig( - text = TextReference.Str(value = "Add"), + text = TextReference.Str(value = "Adddddddd"), onClick = {}, ) PrimarySmallButton(config = config) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 02cf8f05ba..9b56a22f57 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow @@ -87,7 +86,6 @@ private fun Button( Row( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) - .clip(shape) .background(color = backgroundColor, shape = shape) .clickable(enabled = config.enabled, onClick = config.onClick) .padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index b603622d2f..11fcaeb099 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -35,6 +35,7 @@ fun TangemButton( elevation: ButtonElevation = TangemButtonsDefaults.elevation, textStyle: TextStyle = TangemTheme.typography.button, shape: Shape = size.toShape(), + iconPadding: Dp = size.toIconPadding(), ) { val multipleClickPreventer = remember { MultipleClickPreventer.get() } @@ -53,7 +54,7 @@ fun TangemButton( ButtonContentContainer( buttonIcon = icon, - iconPadding = size.toIconPadding(), + iconPadding = iconPadding, showProgress = showProgress, progressIndicator = { CircularProgressIndicator( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt index df18f78ab0..44d622f9eb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -1,10 +1,16 @@ package com.tangem.core.ui.components.buttons.segmentedbutton +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.* @@ -58,11 +64,16 @@ inline fun SegmentedButtons( val leftRadius = if (index == 0) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 + val animateColor by animateColorAsState( + targetValue = if (index == selected) selectedColor else color, + label = "Segmented Button Selected Color Animation", + animationSpec = spring(stiffness = Spring.StiffnessMedium), + ) Box( modifier = Modifier .weight(1f) .background( - color = if (index == selected) selectedColor else color, + color = animateColor, shape = RoundedCornerShape( topStart = leftRadius, topEnd = rightRadius, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index 59c9b5143d..4bf1b72d84 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components.fields import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.* @@ -54,6 +55,7 @@ fun AmountTextField( keyboardOptions: KeyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, ), + keyboardActions: KeyboardActions = KeyboardActions.Default, ) { val decimalFormat = rememberDecimalFormat() @@ -75,6 +77,7 @@ fun AmountTextField( textStyle = textStyle, color = color, keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, singleLine = true, visualTransformation = AmountVisualTransformation(decimals, symbol, decimalFormat), decorationBox = { innerTextField -> diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 1e29006979..4a3fdbff20 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -1,7 +1,9 @@ package com.tangem.core.ui.components.fields +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.LocalTextSelectionColors import androidx.compose.foundation.text.selection.TextSelectionColors @@ -33,6 +35,7 @@ fun SimpleTextField( singleLine: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, color: Color = TangemTheme.colors.text.primary1, textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), readOnly: Boolean = false, @@ -42,26 +45,31 @@ fun SimpleTextField( mutableStateOf( TextFieldValue( text = value, - selection = when { - value.isEmpty() -> TextRange.Zero - else -> TextRange(value.length, value.length) - }, + selection = getValueRange(value), ), ) } val focusRequester = remember { FocusRequester.Default } val customTextSelectionColors = TextSelectionColors( - handleColor = TangemTheme.colors.text.secondary, - backgroundColor = TangemTheme.colors.text.secondary.copy(alpha = 0.4f), + handleColor = TangemTheme.colors.text.accent, + backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), ) val textFieldValue = textFieldValueState.copy(text = value) - SideEffect { - if (textFieldValue.selection != textFieldValueState.selection || - textFieldValue.composition != textFieldValueState.composition - ) { - textFieldValueState = textFieldValue + val isSelectionChanged by remember { + derivedStateOf { + textFieldValue.selection != textFieldValueState.selection || + textFieldValue.composition != textFieldValueState.composition || + textFieldValue.text != textFieldValueState.text + } + } + + LaunchedEffect(key1 = isSelectionChanged) { + if (isSelectionChanged) { + textFieldValueState = textFieldValue.copy( + selection = getValueRange(value), + ) } } @@ -86,20 +94,46 @@ fun SimpleTextField( readOnly = readOnly, visualTransformation = visualTransformation, keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, decorationBox = decorationBox ?: { textValue -> - Box { - if (value.isBlank() && placeholder != null) { - Text( - text = placeholder.resolveReference(), - style = textStyle, - color = TangemTheme.colors.text.disabled, - ) - } - textValue() - } + SimpleTextPlaceholder( + placeholder = placeholder, + value = value, + textStyle = textStyle, + textValue = textValue, + ) }, modifier = modifier .focusRequester(focusRequester), ) } +} + +private fun getValueRange(value: String) = when { + value.isEmpty() -> TextRange.Zero + else -> TextRange(value.length, value.length) +} + +@Composable +private fun SimpleTextPlaceholder( + placeholder: TextReference?, + value: String, + textStyle: TextStyle, + textValue: @Composable () -> Unit, +) { + Box { + if (value.isBlank() && placeholder != null) { + AnimatedContent( + targetState = placeholder, + label = "Placeholder Change Animation", + ) { + Text( + text = it.resolveReference(), + style = textStyle, + color = TangemTheme.colors.text.disabled, + ) + } + } + textValue() + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt index d25b9e7bc4..dd7c375e77 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterAmount.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon @@ -51,6 +52,7 @@ fun InputRowEnterAmount( titleColor: Color = TangemTheme.colors.text.secondary, textColor: Color = TangemTheme.colors.text.primary1, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, iconRes: Int? = null, iconTint: Color = TangemTheme.colors.icon.informative, onIconClick: (() -> Unit)? = null, @@ -79,6 +81,7 @@ fun InputRowEnterAmount( color = textColor, textStyle = TangemTheme.typography.body2, keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, modifier = Modifier .padding(top = TangemTheme.dimens.spacing8), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt index 273e0ab9ab..40276abe7a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -49,6 +50,7 @@ fun InputRowEnterInfoAmount( textColor: Color = TangemTheme.colors.text.primary1, infoColor: Color = TangemTheme.colors.text.tertiary, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, showDivider: Boolean = false, ) { DividerContainer( @@ -74,6 +76,7 @@ fun InputRowEnterInfoAmount( color = textColor, textStyle = TangemTheme.typography.body2, keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, modifier = Modifier .padding(top = TangemTheme.dimens.spacing8) .weight(1f), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index e58a5bd0a8..c16d459a12 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.CenterEnd import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -16,6 +17,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.R import com.tangem.core.ui.components.fields.SimpleTextField import com.tangem.core.ui.components.icons.identicon.IdentIcon +import com.tangem.core.ui.components.inputrow.inner.CrossIcon import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.components.inputrow.inner.PasteButton import com.tangem.core.ui.extensions.TextReference @@ -73,44 +75,37 @@ fun InputRowRecipient( color = color, ) } - Row( + Box( modifier = Modifier .padding(top = TangemTheme.dimens.spacing8), ) { - AnimatedContent( - targetState = isLoading, - label = "Indicator Show Change", - modifier = Modifier - .align(CenterVertically) - .clip(RoundedCornerShape(TangemTheme.dimens.radius18)) - .size(TangemTheme.dimens.size36) - .background(TangemTheme.colors.background.tertiary), - ) { showIndicator -> - if (showIndicator) { - CircularProgressIndicator( - color = TangemTheme.colors.icon.informative, - modifier = Modifier - .padding(TangemTheme.dimens.spacing8), - ) - } else { - IdentIcon(address = value) - } + Row { + InputIcon( + isLoading = isLoading, + value = value, + ) + SimpleTextField( + value = value, + placeholder = placeholder, + onValueChange = onValueChange, + singleLine = singleLine, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing12) + .weight(1f) + .align(CenterVertically), + ) + CrossIcon( + onClick = onPasteClick, + modifier = Modifier + .align(CenterVertically) + .padding(start = TangemTheme.dimens.spacing8), + ) } - SimpleTextField( - value = value, - placeholder = placeholder, - onValueChange = onValueChange, - singleLine = singleLine, - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing12) - .weight(1f) - .align(CenterVertically), - ) PasteButton( isPasteButtonVisible = value.isBlank(), onClick = onPasteClick, modifier = Modifier - .align(CenterVertically) + .align(CenterEnd) .padding(start = TangemTheme.dimens.spacing8), ) } @@ -118,6 +113,29 @@ fun InputRowRecipient( } } +@Composable +private fun RowScope.InputIcon(isLoading: Boolean, value: String) { + AnimatedContent( + targetState = isLoading, + label = "Indicator Show Change", + modifier = Modifier + .align(CenterVertically) + .clip(RoundedCornerShape(TangemTheme.dimens.radius18)) + .size(TangemTheme.dimens.size36) + .background(TangemTheme.colors.background.tertiary), + ) { showIndicator -> + if (showIndicator) { + CircularProgressIndicator( + color = TangemTheme.colors.icon.informative, + modifier = Modifier + .padding(TangemTheme.dimens.spacing8), + ) + } else { + IdentIcon(address = value) + } + } +} + //region preview @Preview @Composable diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt index 3e4642c061..1d7d0fab95 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt @@ -1,24 +1,28 @@ package com.tangem.core.ui.components.inputrow.inner +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.DEFAULT_ANIMATION_DURATION /** * Paste button with cross icon. Retrieves text from clipboard. @@ -33,48 +37,67 @@ fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifi val clipboardManager = LocalClipboardManager.current val hapticFeedback = LocalHapticFeedback.current - if (isPasteButtonVisible) { - Box(modifier = modifier) { - Text( - text = "Paste", - style = TangemTheme.typography.button, - color = TangemTheme.colors.text.primary2, - modifier = Modifier - .background( - color = TangemTheme.colors.button.primary, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .padding( - horizontal = TangemTheme.dimens.spacing10, - vertical = TangemTheme.dimens.spacing2, - ) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(radius = TangemTheme.dimens.radius8), - onClick = { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - onClick( - clipboardManager - .getText() - ?.toString() - .orEmpty(), - ) - }, - ), - ) - } - } else { - Icon( - painter = painterResource(id = R.drawable.ic_close_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = stringResource(R.string.common_close), - modifier = modifier - .size(TangemTheme.dimens.size20) + AnimatedVisibility( + visible = isPasteButtonVisible, + label = "Paste Button Visibility Animation", + enter = fadeIn(), + exit = fadeOut(animationSpec = tween(DEFAULT_ANIMATION_DURATION)), + modifier = modifier, + ) { + Text( + text = "Paste", + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary2, + modifier = Modifier + .background( + color = TangemTheme.colors.button.primary, + shape = TangemTheme.shapes.roundedCornersXMedium, + ) + .padding( + horizontal = TangemTheme.dimens.spacing10, + vertical = TangemTheme.dimens.spacing2, + ) .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(radius = TangemTheme.dimens.radius10), - onClick = { onClick("") }, + indication = rememberRipple(radius = TangemTheme.dimens.radius8), + onClick = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + onClick( + clipboardManager + .getText() + ?.toString() + .orEmpty(), + ) + }, ), ) } +} + +@Composable +fun CrossIcon(onClick: (String) -> Unit, modifier: Modifier = Modifier) { + Icon( + painter = painterResource(id = R.drawable.ic_close_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = stringResource(R.string.common_close), + modifier = modifier + .size(TangemTheme.dimens.size20) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(radius = TangemTheme.dimens.radius10), + onClick = { onClick("") }, + ), + ) +} + +@Preview +@Composable +private fun PasteButtonPreview() { + var isVisible by remember { mutableStateOf(true) } + TangemTheme { + PasteButton( + isPasteButtonVisible = isVisible, + onClick = { isVisible = !isVisible }, + ) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt index 3ff8fbb6fd..1626a42bce 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt @@ -3,7 +3,6 @@ package com.tangem.core.ui.components.marketprice import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.* @@ -35,39 +34,60 @@ import com.tangem.core.ui.utils.BigDecimalFormatter */ @Composable fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) { - var rootWidth by remember { mutableIntStateOf(value = 0) } - - Column( + Row( modifier = modifier .background( color = TangemTheme.colors.background.primary, - shape = RoundedCornerShape(TangemTheme.dimens.radius14), + shape = TangemTheme.shapes.roundedCornersXMedium, ) - .heightIn(min = TangemTheme.dimens.size70) .fillMaxWidth() - .padding(all = TangemTheme.dimens.spacing14) - .onSizeChanged { rootWidth = it.width }, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6), - horizontalAlignment = Alignment.Start, + .heightIn(min = TangemTheme.dimens.size72) + .padding(all = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, ) { - Title(currencyName = state.currencySymbol) + var rootWidth by remember { mutableIntStateOf(value = 0) } - Content(state = state, rootWidth = rootWidth) + Column( + modifier = Modifier.onSizeChanged { rootWidth = it.width }, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + horizontalAlignment = Alignment.Start, + ) { + Title(currencyName = state.currencySymbol) + Content(state = state, rootWidth = rootWidth) + } + + Icon( + modifier = Modifier.size(TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_chevron_right_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) } } @Composable -private fun Title(currencyName: String) { - Text( - text = stringResource(id = R.string.wallet_marketplace_block_title, currencyName), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.subtitle2, - ) +private fun Title(currencyName: String, modifier: Modifier = Modifier) { + Box( + modifier = modifier.heightIn(min = TangemTheme.dimens.size24), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = stringResource(id = R.string.wallet_marketplace_block_title, currencyName), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.subtitle2, + ) + } } @Composable -private fun Content(state: MarketPriceBlockState, rootWidth: Int) { - AnimatedContent(targetState = state, label = "Update the content") { marketPriceBlockState -> +private fun Content(state: MarketPriceBlockState, rootWidth: Int, modifier: Modifier = Modifier) { + AnimatedContent( + modifier = modifier.heightIn(min = TangemTheme.dimens.size20), + targetState = state, + contentAlignment = Alignment.CenterStart, + label = "Update the content", + ) { marketPriceBlockState -> when (marketPriceBlockState) { is MarketPriceBlockState.Content, is MarketPriceBlockState.Error, @@ -97,6 +117,7 @@ private fun PriceContent(state: MarketPriceBlockState, priceWidthDp: Dp) { @Composable private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) { val priceModifier = Modifier.widthIn(max = priceWidthDp) + AnimatedContent(targetState = state, label = "Update the price block") { marketPriceBlockState -> if (marketPriceBlockState is MarketPriceBlockState.Content) { Row( @@ -127,12 +148,17 @@ private fun Price(price: String, modifier: Modifier = Modifier) { @Composable private fun PriceChangeInPercent(config: PriceChangeState.Content) { - AnimatedContent(targetState = config.type, label = "Update price change") { type -> + AnimatedContent( + targetState = config.type, + contentAlignment = Alignment.CenterStart, + label = "Update price change", + ) { type -> Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4), + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2), ) { Icon( + modifier = Modifier.size(TangemTheme.dimens.size8), painter = painterResource( id = when (type) { PriceChangeType.UP -> R.drawable.ic_arrow_up_8 @@ -184,7 +210,8 @@ private fun QuoteTimeStatus() { ) } -@Preview +// region Preview +@Preview(showBackground = true, widthDp = 360) @Composable private fun Preview_MarketPriceBlock_Light( @PreviewParameter(WalletMarketPriceBlockStateProvider::class) @@ -195,7 +222,7 @@ private fun Preview_MarketPriceBlock_Light( } } -@Preview +@Preview(showBackground = true, widthDp = 360) @Composable private fun Preview_MarketPriceBlock_Dark( @PreviewParameter(WalletMarketPriceBlockStateProvider::class) @@ -227,4 +254,5 @@ private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterPr MarketPriceBlockState.Loading(currencySymbol = "BTC"), MarketPriceBlockState.Error(currencySymbol = "BTC"), ), -) \ No newline at end of file +) +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index 3e863a35f6..271e85f311 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -30,6 +31,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState.Content import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import java.util.UUID @@ -46,16 +48,21 @@ import java.util.UUID [REDACTED_AUTHOR] */ @Composable +@Suppress("LongMethod") fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { Surface( modifier = modifier .background(TangemTheme.colors.background.primary) - .defaultMinSize(minHeight = TangemTheme.dimens.size56) .clickable( enabled = state is TransactionState.Content, onClick = (state as? TransactionState.Content)?.onClick ?: {}, ) - .padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing10), + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size56) + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ), color = TangemTheme.colors.background.primary, ) { @Suppress("DestructuringDeclarationWithTooManyEntries") @@ -76,21 +83,30 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod Title( state = state, modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing12) + .padding( + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing4, + ) .constrainAs(titleItem) { + top.linkTo(parent.top) + bottom.linkTo(subtitleItem.top) start.linkTo(iconItem.end) - top.linkTo(iconItem.top) + end.linkTo(amountItem.start) + width = Dimension.fillToConstraints }, ) Subtitle( state = state, modifier = Modifier - .padding(top = TangemTheme.dimens.spacing6) - .padding(horizontal = TangemTheme.dimens.spacing12) + .padding( + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing4, + ) .constrainAs(subtitleItem) { + top.linkTo(titleItem.bottom) + bottom.linkTo(parent.bottom) start.linkTo(iconItem.end) - top.linkTo(amountItem.bottom) end.linkTo(timestampItem.start) width = Dimension.fillToConstraints }, @@ -100,8 +116,9 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod state = state, isBalanceHidden = isBalanceHidden, modifier = Modifier.constrainAs(amountItem) { + top.linkTo(parent.top) + bottom.linkTo(timestampItem.top) start.linkTo(titleItem.end) - top.linkTo(titleItem.top) end.linkTo(parent.end) width = Dimension.fillToConstraints }, @@ -109,12 +126,11 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod Timestamp( state = state, - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing6) - .constrainAs(timestampItem) { - top.linkTo(amountItem.bottom) - end.linkTo(parent.end) - }, + modifier = Modifier.constrainAs(timestampItem) { + top.linkTo(amountItem.bottom) + bottom.linkTo(parent.bottom) + end.linkTo(parent.end) + }, ) } } @@ -173,11 +189,16 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) { private fun Title(state: TransactionState, modifier: Modifier = Modifier) { when (state) { is TransactionState.Content -> { - Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6), + ) { Text( text = state.title.resolveReference(), color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.subtitle2, + overflow = TextOverflow.Ellipsis, + maxLines = 1, ) if (state.status is Status.Unconfirmed) { @@ -266,7 +287,7 @@ private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) { when (state) { is TransactionState.Content -> { Text( - text = state.timestamp, + text = state.time, modifier = modifier, textAlign = TextAlign.End, color = TangemTheme.colors.text.tertiary, @@ -321,89 +342,109 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider< TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "-0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Confirmed, direction = Direction.OUTGOING, iconRes = R.drawable.ic_arrow_up_24, title = resourceReference(R.string.common_transfer), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Unconfirmed, direction = Direction.INCOMING, iconRes = R.drawable.ic_arrow_down_24, title = resourceReference(R.string.common_transfer), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Unconfirmed, direction = Direction.OUTGOING, iconRes = R.drawable.ic_doc_24, title = resourceReference(R.string.common_approval), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Failed, direction = Direction.OUTGOING, iconRes = R.drawable.ic_doc_24, title = resourceReference(R.string.common_approval), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Confirmed, direction = Direction.OUTGOING, iconRes = R.drawable.ic_doc_24, title = resourceReference(R.string.common_approval), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Unconfirmed, direction = Direction.INCOMING, iconRes = R.drawable.ic_arrow_down_24, title = resourceReference(R.string.common_swap), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Confirmed, direction = Direction.INCOMING, iconRes = R.drawable.ic_doc_24, title = TextReference.Str("Submit"), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, onClick = {}, ), TransactionState.Content( txHash = UUID.randomUUID().toString(), amount = "+0.500913 BTC", - timestamp = "8:41", + time = "8:41", status = Status.Confirmed, direction = Direction.OUTGOING, iconRes = R.drawable.ic_arrow_up_24, title = TextReference.Str("Submit"), subtitle = TextReference.Str("33BddS...ga2B"), + timestamp = 0L, + onClick = {}, + ), + TransactionState.Content( + txHash = UUID.randomUUID().toString(), + amount = "0.625 USDT", + time = "€0.50", + status = Status.Confirmed, + direction = Direction.OUTGOING, + iconRes = R.drawable.ic_arrow_up_24, + title = TextReference.Str("Unlimint Banking Cards Sandbox London"), + subtitle = stringReference(value = "8:41 • authorized"), + timestamp = 0L, onClick = {}, ), TransactionState.Loading(txHash = UUID.randomUUID().toString()), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt index 4676fd5c49..b2b02d9e1a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt @@ -1,12 +1,14 @@ package com.tangem.core.ui.components.transactions import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState import com.tangem.core.ui.res.TangemTheme @@ -20,19 +22,23 @@ import java.util.UUID */ @Composable internal fun TxHistoryGroupTitle(config: TxHistoryItemState.GroupTitle, modifier: Modifier = Modifier) { - Text( - text = config.title, + Box( modifier = modifier .background(TangemTheme.colors.background.primary) - .fillMaxWidth() .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing14, - ), - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Start, - style = TangemTheme.typography.body2, - ) + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size24), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = config.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + ) + } } @Preview diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt index 89a804b588..352c19d181 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -24,9 +25,11 @@ internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Mod Row( modifier = modifier .background(TangemTheme.colors.background.primary) - .fillMaxWidth() .padding(top = TangemTheme.dimens.spacing12) - .padding(horizontal = TangemTheme.dimens.spacing16), + .padding(horizontal = TangemTheme.dimens.spacing12) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size24), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { Text( @@ -37,13 +40,14 @@ internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Mod Row( modifier = Modifier.clickable(onClick = onExploreClick), - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2), ) { Icon( + modifier = Modifier.size(size = TangemTheme.dimens.size20), painter = painterResource(id = R.drawable.ic_compass_24), - contentDescription = null, - modifier = Modifier.size(size = TangemTheme.dimens.size18), tint = TangemTheme.colors.icon.informative, + contentDescription = null, ) Text( text = stringResource(id = R.string.common_explorer), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt index 7f85f98d19..c2b7368924 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt @@ -1,8 +1,8 @@ package com.tangem.core.ui.components.transactions.empty -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -33,9 +33,10 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), horizontalAlignment = Alignment.CenterHorizontally, ) { - Image( + Icon( modifier = Modifier.size(TangemTheme.dimens.size64), painter = painterResource(id = state.iconRes), + tint = TangemTheme.colors.icon.inactive, contentDescription = null, ) @@ -44,7 +45,7 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier textAlign = TextAlign.Center, text = state.text.resolveReference(), style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, + color = TangemTheme.colors.text.tertiary, ) Buttons( @@ -84,8 +85,8 @@ private fun PairButtons(state: EmptyTransactionsBlockState.ButtonsState.PairButt } } -@Preview @Composable +@Preview(widthDp = 360, showBackground = true) private fun EmptyTransactionBlock_Light( @PreviewParameter(EmptyTransactionBlockStateProvider::class) state: EmptyTransactionsBlockState, ) { @@ -94,8 +95,8 @@ private fun EmptyTransactionBlock_Light( } } -@Preview @Composable +@Preview(widthDp = 360, showBackground = true) private fun EmptyTransactionBlock_Dark( @PreviewParameter(EmptyTransactionBlockStateProvider::class) state: EmptyTransactionsBlockState, ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt index c18e43f28e..7df1982a29 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt @@ -25,13 +25,14 @@ sealed interface TransactionState { data class Content( override val txHash: String, val amount: String, - val timestamp: String, + val time: String, val status: Status, val direction: Direction, val onClick: () -> Unit, @DrawableRes val iconRes: Int, val title: TextReference, val subtitle: TextReference, + val timestamp: Long, ) : TransactionState { sealed class Status { diff --git a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt index 4a014cd824..78a23e7b11 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt @@ -19,7 +19,7 @@ fun Modifier.roundedShapeItemDecoration( modifier .then( if (addDefaultPadding) { - Modifier.padding(top = TangemTheme.dimens.spacing14) + Modifier.padding(top = TangemTheme.dimens.spacing12) } else { Modifier }, @@ -30,7 +30,7 @@ fun Modifier.roundedShapeItemDecoration( modifier .then( if (addDefaultPadding) { - Modifier.padding(top = TangemTheme.dimens.spacing14) + Modifier.padding(top = TangemTheme.dimens.spacing12) } else { Modifier }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index e3170a4467..c5a3195582 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -49,6 +49,10 @@ fun getActiveIconRes(blockchainId: String): Int { "xdc", "xdc/test" -> R.drawable.img_xdc_22 "vechain", "vechain/test" -> R.drawable.img_vechain_22 "aptos", "aptos/test" -> R.drawable.img_aptos_22 + "shibarium", "shibarium/test" -> R.drawable.img_shibarium_22 + "algorand", "algorand/test" -> R.drawable.img_algorand_22 + "hedera", "hedera/test" -> R.drawable.img_hedera_22 + "playa3ull" -> R.drawable.img_playa3ull_22 else -> R.drawable.ic_alert_24 } } @@ -99,6 +103,10 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "xdc-network", "xdc-network/test" -> R.drawable.img_xdc_22 "vechain", "vechain/test" -> R.drawable.img_vechain_22 "aptos", "aptos/test" -> R.drawable.img_aptos_22 + "shibarium", "shibarium/test" -> R.drawable.img_shibarium_22 + "algorand", "algorand/test" -> R.drawable.img_algorand_22 + "hedera-hashgraph", "hedera/test" -> R.drawable.img_hedera_22 + "playa3ull-games" -> R.drawable.img_playa3ull_22 else -> R.drawable.ic_alert_24 } } @@ -146,6 +154,10 @@ fun getActiveIconResByCoinId(coinId: String): Int { "xdce-crowd-sale" -> R.drawable.img_xdc_22 "vechain" -> R.drawable.img_vechain_22 "aptos" -> R.drawable.img_aptos_22 + "shibarium" -> R.drawable.img_shibarium_22 + "algorand" -> R.drawable.img_algorand_22 + "hedera-hashgraph" -> R.drawable.img_hedera_22 + "playa3ull-games-2" -> R.drawable.img_playa3ull_22 else -> R.drawable.ic_alert_24 } } @@ -196,6 +208,10 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "xdc", "xdc/test" -> R.drawable.ic_xdc_22 "vechain", "vechain/test" -> R.drawable.ic_vechain_22 "aptos", "aptos/test" -> R.drawable.ic_aptos_22 + "shibarium", "shibarium/test" -> R.drawable.ic_shibarium_22 + "algorand", "algorand/test" -> R.drawable.ic_algorand_22 + "hedera", "hedera/test" -> R.drawable.ic_hedera_22 + "playa3ull" -> R.drawable.ic_playa3ull_22 else -> R.drawable.ic_alert_24 } } @@ -246,6 +262,10 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "xdc-network", "xdc-network/test" -> R.drawable.ic_xdc_22 "vechain", "vechain/test" -> R.drawable.ic_vechain_22 "aptos", "aptos/test" -> R.drawable.ic_aptos_22 + "shibarium", "shibarium/test" -> R.drawable.ic_shibarium_22 + "algorand", "algorand/test" -> R.drawable.ic_algorand_22 + "hedera-hashgraph", "hedera/test" -> R.drawable.ic_hedera_22 + "playa3ull-games" -> R.drawable.ic_playa3ull_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ComposeNavigationExt.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ComposeNavigationExt.kt new file mode 100644 index 0000000000..2dc5bcd17e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ComposeNavigationExt.kt @@ -0,0 +1,49 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import timber.log.Timber + +/** + * The ViewModel is scoped to the parent route Navigation graph + * and is provided using the Hilt-generated ViewModel factory + * + * ``` + * val navController = rememberNavController() + * + * navigation( + * route = "parent", + * startDestination = "parent/1" + * ) { + * composable("route/1") { entry -> + * val viewModel = entry.parentHiltViewModel(navController) + * } + * composable("route/2") { entry -> + * val viewModel = entry.parentHiltViewModel(navController) + * } + * composable("route/3") { entry -> + * val viewModel = entry.parentHiltViewModel(navController) + * } + * } + * ``` + * + * @param navController NavController within the common NavGraph + * @throws Exception if there is no parent route + */ +@Composable +inline fun NavBackStackEntry.parentHiltViewModel(navController: NavController): T { + val viewModelStoreOwner = remember(this) { + try { + navController.getBackStackEntry(this.destination.parent!!.id) + } catch (e: Exception) { + Timber.tag("scopedViewModel").e(e, "There is no parent route'") + throw e + } + } + + return hiltViewModel(viewModelStoreOwner) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index e68dfd96a0..13ec4eb908 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -68,6 +68,7 @@ data class TangemDimens internal constructor( val size50: Dp = 50.dp, val size52: Dp = 52.dp, val size56: Dp = 56.dp, + val size60: Dp = 60.dp, val size62: Dp = 62.dp, val size64: Dp = 64.dp, val size68: Dp = 68.dp, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/AnimationUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/AnimationUtils.kt new file mode 100644 index 0000000000..e43855563b --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/AnimationUtils.kt @@ -0,0 +1,3 @@ +package com.tangem.core.ui.utils + +const val DEFAULT_ANIMATION_DURATION = 300 \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt index dc27c1641a..ce8b600e55 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt @@ -55,6 +55,10 @@ object DateTimeFormatters { .withLocale(Locale.getDefault()) } + val dateTimeFormatter: DateTimeFormatter by lazy { + DateTimeFormat.forPattern("dd.MM.yyyy HH:mm") + } + fun formatTime(formatter: DateTimeFormatter = timeFormatter, time: DateTime): String { return formatter.print(time) } diff --git a/core/ui/src/main/res/drawable/ic_algorand_22.xml b/core/ui/src/main/res/drawable/ic_algorand_22.xml new file mode 100644 index 0000000000..770f3c6096 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_algorand_22.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_empty_token_64.xml b/core/ui/src/main/res/drawable/ic_empty_token_64.xml index 51821ac760..fa38ac65a5 100644 --- a/core/ui/src/main/res/drawable/ic_empty_token_64.xml +++ b/core/ui/src/main/res/drawable/ic_empty_token_64.xml @@ -3,22 +3,8 @@ android:height="64dp" android:viewportWidth="64" android:viewportHeight="64"> - - - - - diff --git a/core/ui/src/main/res/drawable/ic_hedera_22.xml b/core/ui/src/main/res/drawable/ic_hedera_22.xml new file mode 100644 index 0000000000..a8ead7c102 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_hedera_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_playa3ull_22.xml b/core/ui/src/main/res/drawable/ic_playa3ull_22.xml new file mode 100644 index 0000000000..ad533a3abb --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_playa3ull_22.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_shibarium_22.xml b/core/ui/src/main/res/drawable/ic_shibarium_22.xml new file mode 100644 index 0000000000..359f911c8f --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_shibarium_22.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_algorand_22.xml b/core/ui/src/main/res/drawable/img_algorand_22.xml new file mode 100644 index 0000000000..3298f10b19 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_algorand_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_hedera_22.xml b/core/ui/src/main/res/drawable/img_hedera_22.xml new file mode 100644 index 0000000000..f96e2befb9 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_hedera_22.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_playa3ull_22.xml b/core/ui/src/main/res/drawable/img_playa3ull_22.xml new file mode 100644 index 0000000000..4a9a544f84 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_playa3ull_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_shibarium_22.xml b/core/ui/src/main/res/drawable/img_shibarium_22.xml new file mode 100644 index 0000000000..683c3555ca --- /dev/null +++ b/core/ui/src/main/res/drawable/img_shibarium_22.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt index a1995e1712..a2533147f7 100644 --- a/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt +++ b/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt @@ -4,7 +4,7 @@ import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat import java.text.NumberFormat -import java.util.* +import java.util.Locale // todo determine where to place this extensions fun BigDecimal.toFormattedString( @@ -38,7 +38,7 @@ fun BigDecimal.toFormattedCurrencyString( decimals = decimalsForRounding, roundingMode = roundingMode, ) - val formattedCurrency = currency?.let { " $it " } ?: "" + val formattedCurrency = currency?.let { " $it" } ?: "" return "$formattedAmount$formattedCurrency" } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/converters/UtxoConverter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/converters/UtxoConverter.kt new file mode 100644 index 0000000000..2a663558c3 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/converters/UtxoConverter.kt @@ -0,0 +1,14 @@ +package com.tangem.data.tokens.converters + +import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.common.UtxoAmountLimit as BlockchainUtxoAmountLimit + +internal class UtxoConverter : Converter { + override fun convert(value: BlockchainUtxoAmountLimit): UtxoAmountLimit { + return UtxoAmountLimit( + maxLimit = value.maxLimit, + maxAmount = value.maxAmount, + ) + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index dba2fda4b9..05c56cfaf9 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -113,4 +113,10 @@ internal object TokensDataModule { ): NetworksCompatibilityRepository { return DefaultNetworksCompatibilityRepository(userWalletsStore = userWalletsStore, dispatchers = dispatchers) } + + @Provides + @Singleton + fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { + return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt new file mode 100644 index 0000000000..8f21f5d495 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -0,0 +1,70 @@ +package com.tangem.data.tokens.repository + +import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider +import com.tangem.blockchain.common.ReserveAmountProvider +import com.tangem.blockchain.common.UtxoAmountLimitProvider +import com.tangem.data.tokens.converters.UtxoConverter +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit +import com.tangem.domain.tokens.repository.CurrencyChecksRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId +import java.math.BigDecimal + +internal class DefaultCurrencyChecksRepository( + private val walletManagersFacade: WalletManagersFacade, +) : CurrencyChecksRepository { + override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + + return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null + } + + override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + + return manager?.dustValue + } + + override suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + + return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null + } + + override suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + + return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true + } + + override suspend fun checkUtxoAmountLimit( + userWalletId: UserWalletId, + network: Network, + amount: BigDecimal, + fee: BigDecimal, + ): UtxoAmountLimit? { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + val utxoAmount = if (manager is UtxoAmountLimitProvider) { + manager.checkUtxoAmountLimit(amount, fee) + } else { + null + } + return utxoAmount?.let(UtxoConverter()::convert) + } +} \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 7c515253c2..76b16b1409 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -1,8 +1,10 @@ package com.tangem.data.transaction import androidx.core.text.isDigitsOnly +import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras +import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder import com.tangem.blockchain.blockchains.stellar.StellarMemo import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras @@ -71,6 +73,8 @@ internal class DefaultTransactionRepository( Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) } Blockchain.Cosmos -> CosmosTransactionExtras(memo) Blockchain.TON -> TonTransactionExtras(memo) + Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo) + Blockchain.Algorand -> AlgorandTransactionExtras(memo) else -> null } } diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt index 5452c7c385..2709c26dfc 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/paging/TxHistoryPagingSource.kt @@ -7,9 +7,11 @@ import com.tangem.data.common.cache.CacheRegistry import com.tangem.datasource.local.txhistory.TxHistoryItemsStore import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.txhistory.models.Page import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.walletmanager.utils.SdkPageConverter import com.tangem.domain.wallets.models.UserWalletId import timber.log.Timber @@ -18,21 +20,19 @@ internal class TxHistoryPagingSource( private val txHistoryItemsStore: TxHistoryItemsStore, private val walletManagersFacade: WalletManagersFacade, private val cacheRegistry: CacheRegistry, -) : PagingSource() { +) : PagingSource() { private val storeKey = TxHistoryItemsStore.Key(sourceParams.userWalletId, sourceParams.currency) + private val sdkPageConverter by lazy { SdkPageConverter() } override val keyReuseSupported: Boolean get() = true - override fun getRefreshKey(state: PagingState): Int? { - return state.anchorPosition?.let { anchorPosition -> - val anchorPage = state.closestPageToPosition(anchorPosition) - anchorPage?.prevKey?.inc() ?: anchorPage?.nextKey?.dec() - } + override fun getRefreshKey(state: PagingState): Page? { + return null } - override suspend fun load(params: LoadParams): LoadResult { - val pageToLoad = params.key ?: INITIAL_PAGE + override suspend fun load(params: LoadParams): LoadResult { + val pageToLoad = params.key ?: Page.Initial return try { val wrappedItems = loadItems( @@ -40,20 +40,11 @@ internal class TxHistoryPagingSource( pageSize = sourceParams.pageSize, refresh = sourceParams.refresh && params is LoadParams.Refresh, ) - - val items = wrappedItems.items - val prevPage = when { - items.isEmpty() -> null - pageToLoad > INITIAL_PAGE -> pageToLoad.dec() - else -> null + val nextKey = when (wrappedItems.nextPage) { + Page.LastPage -> null + Page.Initial, is Page.Next -> wrappedItems.nextPage } - val nextPage = when { - items.isEmpty() -> INITIAL_PAGE - pageToLoad < wrappedItems.totalPages -> pageToLoad.inc() - else -> null - } - - LoadResult.Page(items, prevKey = prevPage, nextKey = nextPage) + LoadResult.Page(wrappedItems.items, prevKey = null, nextKey = nextKey) } catch (e: Throwable) { Timber.e(e, "Unable to load the transaction history for the requested page: $pageToLoad") @@ -61,7 +52,7 @@ internal class TxHistoryPagingSource( } } - private suspend fun loadItems(pageToLoad: Int, pageSize: Int, refresh: Boolean): PaginationWrapper { + private suspend fun loadItems(pageToLoad: Page, pageSize: Int, refresh: Boolean): PaginationWrapper { cacheRegistry.invokeOnExpire( key = getTxHistoryPageKey(pageToLoad), skipCache = refresh, @@ -71,23 +62,23 @@ internal class TxHistoryPagingSource( return txHistoryItemsStore.getSync(pageToLoad) } - private suspend fun fetch(pageToLoad: Int, pageSize: Int) { + private suspend fun fetch(pageToLoad: Page, pageSize: Int) { val wrappedItems = walletManagersFacade.getTxHistoryItems( userWalletId = sourceParams.userWalletId, currency = sourceParams.currency, - page = pageToLoad, + page = sdkPageConverter.convertBack(pageToLoad), pageSize = pageSize, ) txHistoryItemsStore.store(key = storeKey, value = wrappedItems) } - private suspend fun TxHistoryItemsStore.getSync(pageToLoad: Int): PaginationWrapper { + private suspend fun TxHistoryItemsStore.getSync(pageToLoad: Page): PaginationWrapper { val storedItems = requireNotNull(getSyncOrNull(storeKey, pageToLoad)) { "The transaction history page #$pageToLoad could not be retrieved" } - return if (pageToLoad == 1) storedItems.addRecentTransactions() else storedItems + return if (pageToLoad is Page.Initial) storedItems.addRecentTransactions() else storedItems } private suspend fun PaginationWrapper.addRecentTransactions(): PaginationWrapper { @@ -131,7 +122,7 @@ internal class TxHistoryPagingSource( return filter { item -> apiItems.none { it.txHash == item.txHash } } } - private fun getTxHistoryPageKey(page: Int): String { + private fun getTxHistoryPageKey(page: Page): String { return "tx_history_page_${sourceParams.currency}_${sourceParams.userWalletId}_$page" } @@ -141,8 +132,4 @@ internal class TxHistoryPagingSource( val pageSize: Int, val refresh: Boolean, ) - - private companion object { - private const val INITIAL_PAGE = 1 - } } \ No newline at end of file diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index e57696df79..d41cf5f9c7 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -11,9 +11,35 @@ android { dependencies { + /** Project - Data */ + implementation(projects.core.datasource) + implementation(projects.data.common) + /** Project - Domain */ implementation(projects.domain.visa) + implementation(projects.domain.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.appCurrency.models) + + /** Project - Utils */ + implementation(projects.core.utils) + implementation(projects.domain.legacy) + + /** Project - Libs */ + implementation(projects.libs.visa) + + /** Libs - Other */ + implementation(deps.kotlin.coroutines) + implementation(deps.arrow.core) + implementation(deps.arrow.fx) + implementation(deps.jodatime) + implementation(deps.timber) + implementation(deps.androidx.paging.runtime) + implementation(deps.moshi.kotlin) + + /** Libs - Tangem */ + implementation(deps.tangem.blockchain) + implementation(deps.tangem.card.core) /** DI */ implementation(deps.hilt.core) diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt new file mode 100644 index 0000000000..d14a201a2b --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt @@ -0,0 +1,190 @@ +package com.tangem.data.visa + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import arrow.fx.coroutines.parZip +import com.tangem.blockchain.common.address.Address +import com.tangem.blockchain.common.address.AddressType +import com.tangem.common.card.EllipticCurve +import com.tangem.common.extensions.toHexString +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.visa.utils.VisaConfig +import com.tangem.data.visa.utils.VisaCurrencyFactory +import com.tangem.data.visa.utils.VisaTxDetailsFactory +import com.tangem.data.visa.utils.VisaTxHistoryPagingSource +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.domain.visa.repository.VisaRepository +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.lib.visa.VisaContractInfoProvider +import com.tangem.lib.visa.api.VisaApi +import com.tangem.lib.visa.model.VisaTxHistoryResponse +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +internal class DefaultVisaRepository( + private val visaContractInfoProvider: VisaContractInfoProvider, + private val tangemTechApi: TangemTechApi, + private val visaApi: VisaApi, + private val cacheRegistry: CacheRegistry, + private val userWalletsStore: UserWalletsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : VisaRepository { + + private val currencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { + VisaCurrencyFactory() + } + private val txDetailsFactory by lazy(mode = LazyThreadSafetyMode.NONE) { + VisaTxDetailsFactory() + } + + private val fetchedCurrencies = MutableStateFlow( + value = hashMapOf(), + ) + private val fetchedHistoryItems = MutableStateFlow( + value = emptyMap>(), + ) + + override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { + val address = makeAddress(userWalletId) + // val address = "0x143fe062a538176aa0bf162f13d390208f90898f" // for testing + + fetchVisaCurrencyIfExpired(address, isRefresh) + + return requireNotNull(fetchedCurrencies.value[address]) { + "Unable to find VISA currency for $address" + } + } + + private suspend fun fetchVisaCurrencyIfExpired(address: String, isRefresh: Boolean) { + cacheRegistry.invokeOnExpire( + key = getBalancesAndLimitsKey(address), + skipCache = isRefresh, + block = { fetchVisaCurrency(address) }, + ) + } + + private suspend fun fetchVisaCurrency(address: String) { + parZip( + dispatchers.io, + { visaContractInfoProvider.getBalancesAndLimits(address) }, + { getFiatRate() }, + { balancesAndLimits, fiatRate -> + fetchedCurrencies.update { value -> + value.apply { + put(address, currencyFactory.create(balancesAndLimits, fiatRate)) + } + } + }, + ) + } + + override suspend fun getTxHistory( + userWalletId: UserWalletId, + pageSize: Int, + isRefresh: Boolean, + ): Flow> { + val userWallet = findVisaUserWallet(userWalletId) + val cardPubKey = getCardPubKey(userWallet).toHexString() + // val cardPubKey = "03DEF02B1FECC8BD3CFD52CE93235194479E1DE931EF0F55DC194967E7CCC3D12C" // for testing + val pager = Pager( + config = PagingConfig( + pageSize = pageSize, + initialLoadSize = pageSize, + ), + pagingSourceFactory = { + VisaTxHistoryPagingSource( + params = VisaTxHistoryPagingSource.Params( + cardPublicKey = cardPubKey, + pageSize = pageSize, + isRefresh = isRefresh, + ), + cacheRegistry = cacheRegistry, + visaApi = visaApi, + fetchedItems = fetchedHistoryItems, + dispatchers = dispatchers, + ) + }, + ) + + return pager.flow + } + + override suspend fun getTxDetails(userWalletId: UserWalletId, txId: String): VisaTxDetails { + return withContext(dispatchers.io) { + val userWallet = findVisaUserWallet(userWalletId) + val cardPubKey = getCardPubKey(userWallet).toHexString() + // val cardPubKey = "03DEF02B1FECC8BD3CFD52CE93235194479E1DE931EF0F55DC194967E7CCC3D12C" // for testing + val transaction = fetchedHistoryItems.value[cardPubKey]?.firstOrNull { + it.transactionId.toString() == txId + } + requireNotNull(transaction) { "Transaction not found: $txId" } + + txDetailsFactory.create( + transaction = transaction, + walletBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain(), + ) + } + } + + private suspend fun makeAddress(userWalletId: UserWalletId): String { + val userWallet = findVisaUserWallet(userWalletId) + val walletAddresses = makeWalletAddresses(userWallet) + val walletAddress = walletAddresses.firstOrNull { it.type == AddressType.Default } + + return requireNotNull(walletAddress?.value) { + "Unable to find wallet address" + } + } + + private suspend fun getFiatRate(): BigDecimal? { + val fiatCurrencyId = VisaConfig.fiatCurrency.code.lowercase() + val quotes = tangemTechApi.getQuotes( + currencyId = fiatCurrencyId, + coinIds = VisaConfig.TOKEN_ID, + ).getOrThrow() + + return quotes.quotes[VisaConfig.TOKEN_ID]?.price + } + + private fun makeWalletAddresses(userWallet: UserWallet): Set
{ + val walletBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() + + return walletBlockchain.makeAddresses(getCardPubKey(userWallet)) + } + + private fun getCardPubKey(userWallet: UserWallet): ByteArray { + val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull { + it.curve == EllipticCurve.Secp256k1 + } + requireNotNull(cardWallet) { "Secp256k1 card wallet not found" } + + return cardWallet.publicKey + } + + private suspend fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { + val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { + "No user wallet found: $userWalletId" + } + if (!userWallet.scanResponse.cardTypesResolver.isVisaWallet()) { + error("VISA wallet required: $userWalletId") + } + + return userWallet + } + + private fun getBalancesAndLimitsKey(address: String): String { + return "visa_balances_and_limits_$address" + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt deleted file mode 100644 index 40815fac3c..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.data.visa - -import com.tangem.domain.visa.model.VisaCurrency -import com.tangem.domain.visa.repository.VisaRepository -import com.tangem.domain.wallets.models.UserWalletId - -internal class DummyVisaRepository : VisaRepository { - - override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { - TODO(reason = "Implement in [REDACTED_JIRA]") - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt index ab68249dd9..a0227da794 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt @@ -1,7 +1,16 @@ package com.tangem.data.visa.di -import com.tangem.data.visa.DummyVisaRepository +import com.squareup.moshi.Moshi +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.visa.BuildConfig +import com.tangem.data.visa.DefaultVisaRepository +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.visa.repository.VisaRepository +import com.tangem.lib.visa.VisaContractInfoProvider +import com.tangem.lib.visa.api.VisaApiBuilder +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,7 +23,30 @@ internal object VisaDataModule { @Provides @Singleton - fun provideVisaRepository(): VisaRepository { - return DummyVisaRepository() + fun provideVisaRepository( + @NetworkMoshi moshi: Moshi, + tangemTechApi: TangemTechApi, + cacheRegistry: CacheRegistry, + userWalletsStore: UserWalletsStore, + dispatchers: CoroutineDispatcherProvider, + ): VisaRepository { + val contractInfoProvider = VisaContractInfoProvider.Builder( + isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED, + dispatchers = dispatchers, + ).build() + val visaApi = VisaApiBuilder( + useDevApi = true, + isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED, + moshi = moshi, + ).build() + + return DefaultVisaRepository( + contractInfoProvider, + tangemTechApi, + visaApi, + cacheRegistry, + userWalletsStore, + dispatchers, + ) } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt new file mode 100644 index 0000000000..b664ad4320 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt @@ -0,0 +1,15 @@ +package com.tangem.data.visa.utils + +import android.os.Build +import timber.log.Timber +import java.util.Currency + +internal fun findCurrencyByNumericCode(code: Int): Currency { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Currency.getAvailableCurrencies().firstOrNull { it.numericCode == code } + ?: Currency.getInstance(VisaConfig.fiatCurrency.code) + } else { + Timber.w("Unable to get currency by numeric code on API level ${Build.VERSION.SDK_INT}") + Currency.getInstance(VisaConfig.fiatCurrency.code) + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConfig.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConfig.kt new file mode 100644 index 0000000000..e450a769cc --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConfig.kt @@ -0,0 +1,18 @@ +package com.tangem.data.visa.utils + +import com.tangem.domain.appcurrency.model.AppCurrency + +internal object VisaConfig { + + const val NETWORK_NAME = "Polygon PoS" + + const val TOKEN_SYMBOL = "USDT" + const val TOKEN_ID = "tether" + const val TOKEN_DECIMALS = 8 + + val fiatCurrency = AppCurrency( + code = "EUR", + name = "Euro", + symbol = "€", + ) +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt new file mode 100644 index 0000000000..92bb35d307 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt @@ -0,0 +1,79 @@ +package com.tangem.data.visa.utils + +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.lib.visa.model.VisaBalancesAndLimits +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.Instant +import java.math.BigDecimal +import java.math.BigInteger + +internal class VisaCurrencyFactory { + + fun create(balancesAndLimits: VisaBalancesAndLimits, fiatRate: BigDecimal?): VisaCurrency { + val now = Instant.now() + val currentLimit = if (balancesAndLimits.limitsChangeDate > now) { + balancesAndLimits.oldLimits + } else { + balancesAndLimits.newLimits + } + + return VisaCurrency( + symbol = VisaConfig.TOKEN_SYMBOL, + networkName = VisaConfig.NETWORK_NAME, + decimals = VisaConfig.TOKEN_DECIMALS, + fiatRate = fiatRate, + fiatCurrency = VisaConfig.fiatCurrency, + balances = with(balancesAndLimits) { + VisaCurrency.Balances( + total = balances.total, + verified = balances.verified, + available = balances.available.forPayment, + blocked = balances.blocked, + debt = balances.debt, + pendingRefund = balances.pendingRefund, + ) + }, + limits = VisaCurrency.Limits( + remainingOtp = getRemainingOtp(currentLimit, now), + remainingNoOtp = getRemainingNoOtp(currentLimit, now), + singleTransaction = currentLimit.singleTransactionLimit, + expirationDate = getLimitsExpirationDate(currentLimit, now), + ), + ) + } + + private fun getRemainingOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal { + if (currentLimit.expirationDate >= now) { + return currentLimit.spendLimit.limit - currentLimit.spendLimit.spent + } + + return currentLimit.spendLimit.limit + } + + private fun getRemainingNoOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal { + if (currentLimit.expirationDate >= now) { + return currentLimit.noOtpLimit.limit - currentLimit.noOtpLimit.spent + } + + return currentLimit.noOtpLimit.limit + } + + private fun getLimitsExpirationDate(currentLimits: VisaBalancesAndLimits.Limits, now: Instant): DateTime { + val expirationDate = if (currentLimits.expirationDate >= now) { + currentLimits.expirationDate.toDateTime() + } else { + val spendPeriodDays = currentLimits.spendPeriodSeconds + .div(BigInteger.valueOf(SECONDS_IN_DAY)) + .toInt() + + now.toDateTime().plusDays(spendPeriodDays) + } + + return expirationDate.withZone(DateTimeZone.getDefault()) + } + + private companion object { + const val SECONDS_IN_DAY = 86_400L + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt new file mode 100644 index 0000000000..ad0bbf4fb3 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt @@ -0,0 +1,49 @@ +package com.tangem.data.visa.utils + +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.lib.visa.model.VisaTxHistoryResponse + +internal class VisaTxDetailsFactory { + + fun create(transaction: VisaTxHistoryResponse.Transaction, walletBlockchain: Blockchain): VisaTxDetails { + return VisaTxDetails( + id = transaction.transactionId.toString(), + type = transaction.transactionType, + status = transaction.transactionStatus, + blockchainAmount = transaction.blockchainAmount, + blockchainFee = transaction.blockchainFee, + transactionAmount = transaction.transactionAmount, + transactionCurrencyCode = transaction.transactionCurrencyCode, + merchantName = transaction.merchantName, + merchantCity = transaction.merchantCity, + merchantCountryCode = transaction.merchantCountryCode, + merchantCategoryCode = transaction.merchantCategoryCode, + fiatCurrency = findCurrencyByNumericCode(transaction.transactionCurrencyCode), + requests = transaction.requests.map { createRequest(it, walletBlockchain) }, + ) + } + + private fun createRequest( + request: VisaTxHistoryResponse.Transaction.Request, + walletBlockchain: Blockchain, + ): VisaTxDetails.Request { + return VisaTxDetails.Request( + billingAmount = request.billingAmount, + billingCurrencyCode = request.billingCurrencyCode, + blockchainAmount = request.blockchainAmount, + blockchainFee = request.blockchainFee, + errorCode = request.errorCode, + requestDate = request.requestDt, + requestStatus = request.requestStatus, + requestType = request.requestType, + transactionAmount = request.transactionAmount, + txCurrencyCode = request.transactionCurrencyCode, + id = request.transactionRequestId.toString(), + txHash = request.txHash, + txStatus = request.txStatus, + fiatCurrency = findCurrencyByNumericCode(request.transactionCurrencyCode), + exploreUrl = request.txHash?.let(walletBlockchain::getExploreTxUrl), + ) + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt new file mode 100644 index 0000000000..86d1439068 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt @@ -0,0 +1,19 @@ +package com.tangem.data.visa.utils + +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.lib.visa.model.VisaTxHistoryResponse + +internal class VisaTxHistoryItemFactory { + + fun create(transaction: VisaTxHistoryResponse.Transaction): VisaTxHistoryItem { + return VisaTxHistoryItem( + id = transaction.transactionId.toString(), + date = transaction.transactionDt, + amount = transaction.blockchainAmount, + fiatAmount = transaction.transactionAmount, + merchantName = transaction.merchantName, + status = transaction.transactionStatus, + fiatCurrency = findCurrencyByNumericCode(transaction.transactionCurrencyCode), + ) + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt new file mode 100644 index 0000000000..4af82f3014 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt @@ -0,0 +1,108 @@ +package com.tangem.data.visa.utils + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.lib.visa.api.VisaApi +import com.tangem.lib.visa.model.VisaTxHistoryResponse +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext +import timber.log.Timber + +internal class VisaTxHistoryPagingSource( + params: Params, + private val cacheRegistry: CacheRegistry, + private val visaApi: VisaApi, + private val fetchedItems: MutableStateFlow>>, + private val dispatchers: CoroutineDispatcherProvider, +) : PagingSource() { + + private val itemsFactory = VisaTxHistoryItemFactory() + + private val cardPublicKey = params.cardPublicKey + private val pageSize = params.pageSize + private val isRefresh = params.isRefresh + + private val pagedItems = MutableStateFlow>>( + value = emptyMap(), + ) + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + + anchorPage?.prevKey?.plus(pageSize) ?: anchorPage?.nextKey?.minus(pageSize) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val offsetToLoad = params.key ?: INITIAL_OFFSET + + return try { + fetchItemsIfExpired(offsetToLoad, pageSize, isRefresh = isRefresh && params is LoadParams.Refresh) + + val items = pagedItems.value[offsetToLoad].orEmpty() + val prevOffset = when { + items.isEmpty() -> null + offsetToLoad > INITIAL_OFFSET -> offsetToLoad - pageSize + else -> null + } + val nextOffset = when { + items.isEmpty() -> INITIAL_OFFSET + items.size % pageSize == 0 -> offsetToLoad + pageSize + else -> null + } + + LoadResult.Page(items, prevOffset, nextOffset) + } catch (e: Throwable) { + Timber.e(e, "Unable to load the transaction history for the requested offset: $offsetToLoad") + LoadResult.Error(e) + } + } + + private suspend fun fetchItemsIfExpired(offset: Int, pageSize: Int, isRefresh: Boolean) { + cacheRegistry.invokeOnExpire( + key = getCacheKey(offset), + skipCache = isRefresh, + block = { fetchItems(offset, pageSize) }, + ) + } + + private suspend fun fetchItems(offset: Int, pageSize: Int) = withContext(dispatchers.io) { + val response = visaApi.getTxHistory( + cardPublicKey = cardPublicKey, + limit = pageSize, + offset = offset, + ).getOrThrow() + + fetchedItems.update { + it.toMutableMap().apply { + this[cardPublicKey] = this[cardPublicKey].orEmpty() + response.transactions + } + } + + pagedItems.update { + it.toMutableMap().apply { + this[offset] = response.transactions.map(itemsFactory::create) + } + } + } + + private fun getCacheKey(offset: Int): String { + return "visa_tx_history_${cardPublicKey}_$offset" + } + + class Params( + val cardPublicKey: String, + val pageSize: Int, + val isRefresh: Boolean, + ) + + private companion object { + const val INITIAL_OFFSET = 0 + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt index 8aad6ceb32..3061e99534 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt @@ -1,21 +1,17 @@ package com.tangem.domain.card import arrow.core.Either -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.operations.derivation.DerivationTaskResponse -// TODO: Convert to class [REDACTED_JIRA] -interface DerivePublicKeysUseCase { +class DerivePublicKeysUseCase( + private val derivationsRepository: DerivationsRepository, +) { - // TODO: delete [REDACTED_JIRA] - @Deprecated(message = "Use invoke(cardId: String?, derivations: Map>) instead") - suspend operator fun invoke( - cardId: String? = null, - derivations: Map>, - ): Either - - suspend operator fun invoke(userWalletId: UserWalletId, currencies: List): Either + suspend operator fun invoke(userWalletId: UserWalletId, currencies: List): Either { + return Either.catch { + derivationsRepository.derivePublicKeys(userWalletId, currencies) + } + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/DomainModuleMessage.kt b/domain/legacy/src/main/java/com/tangem/domain/DomainModuleMessage.kt deleted file mode 100644 index af32f7c703..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/DomainModuleMessage.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.domain - -import com.tangem.common.module.ModuleError -import com.tangem.common.module.ModuleErrorCode -import com.tangem.common.module.ModuleMessage - -/** -[REDACTED_AUTHOR] - * All DomainError descendants must use their own range of codes, but no more than 999 error codes for each. - */ -sealed interface DomainModuleMessage : ModuleMessage - -sealed class DomainModuleError( - subCode: Int, - override val message: String, - override val data: Any?, -) : DomainModuleMessage, ModuleError() { - override val code: Int = ModuleErrorCode.DOMAIN + subCode - - companion object { - // base code used for all errors in the module - internal const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 -// const val CODE_ANY_OTHER = 200..299, 300..399 etc - } -} - -sealed class AddCustomTokenError( - subCode: Int = 0, - message: String? = null, - data: Any? = null, -) : DomainModuleError( - subCode = ERROR_CODE_ADD_CUSTOM_TOKEN + subCode, - message = message ?: this::class.java.simpleName, - data = data, -) { - - object FieldIsEmpty : AddCustomTokenError() - object InvalidContractAddress : AddCustomTokenError() -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 9d0357e1f1..86fc4fd0be 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -80,6 +80,13 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "vechain/test" -> Blockchain.VeChainTestnet "aptos" -> Blockchain.Aptos "aptos/test" -> Blockchain.AptosTestnet + "playa3ull-games" -> Blockchain.Playa3ull + "shibarium" -> Blockchain.Shibarium + "shibarium/test" -> Blockchain.ShibariumTestnet + "algorand" -> Blockchain.Algorand + "algorand/test" -> Blockchain.AlgorandTestnet + "hedera-hashgraph" -> Blockchain.Hedera + "hedera-hashgraph/test" -> Blockchain.HederaTestnet else -> null } } @@ -161,6 +168,13 @@ fun Blockchain.toNetworkId(): String { Blockchain.VeChainTestnet -> "vechain/test" Blockchain.Aptos -> "aptos" Blockchain.AptosTestnet -> "aptos/test" + Blockchain.Playa3ull -> "playa3ull-games" + Blockchain.Shibarium -> "shibarium" + Blockchain.ShibariumTestnet -> "shibarium/test" + Blockchain.Algorand -> "algorand" + Blockchain.AlgorandTestnet -> "algorand/test" + Blockchain.Hedera -> "hedera-hashgraph" + Blockchain.HederaTestnet -> "hedera-hashgraph/test" } } @@ -212,7 +226,14 @@ fun Blockchain.toCoinId(): String { Blockchain.VeChain, Blockchain.VeChainTestnet -> "vechain" Blockchain.Aptos -> "aptos" Blockchain.AptosTestnet -> "aptos/test" + Blockchain.Playa3ull -> "playa3ull-games-2" + Blockchain.Shibarium -> "bone-shibaswap" + Blockchain.ShibariumTestnet -> "bone-shibaswap/test" + Blockchain.Algorand -> "algorand" + Blockchain.AlgorandTestnet -> "algorand/test" Blockchain.Unknown -> "unknown" + Blockchain.Hedera -> "hedera-hashgraph" + Blockchain.HederaTestnet -> "hedera-hashgraph/test" } } @@ -240,5 +261,4 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5 private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Ducatus, - Blockchain.Aptos, ) \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/tokens/TokensAction.kt b/domain/legacy/src/main/java/com/tangem/domain/tokens/TokensAction.kt index d5a0d8be8e..90378e19c2 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/tokens/TokensAction.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/tokens/TokensAction.kt @@ -2,8 +2,6 @@ package com.tangem.domain.tokens import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWallet import org.rekotlin.Action sealed interface TokensAction : Action { @@ -13,14 +11,6 @@ sealed interface TokensAction : Action { object ManageAccess : SetArgs object ReadAccess : SetArgs } - - data class SaveChanges( - val currentTokens: List, - val currentCoins: List, - val changedTokens: List, - val changedCoins: List, - val userWallet: UserWallet, - ) : TokensAction } data class TokenWithBlockchain(val token: Token, val blockchain: Blockchain) \ 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 213a569e11..bb0cc5a3ad 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 @@ -5,12 +5,13 @@ import arrow.core.raise.either import arrow.core.raise.ensureNotNull import arrow.core.right import com.squareup.moshi.Moshi -import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.common.address.EstimationFeeAddressFactory +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.blockchain.common.pagination.Page import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest @@ -40,24 +41,33 @@ import timber.log.Timber import java.math.BigDecimal import java.util.EnumSet -@Suppress("LargeClass", "TooManyFunctions") +@Suppress("LargeClass", "TooManyFunctions", "LongParameterList") // FIXME: Move to its own module and make internal @Deprecated("Inject the WalletManagerFacade interface using DI instead") class DefaultWalletManagersFacade( private val walletManagersStore: WalletManagersStore, private val userWalletsStore: UserWalletsStore, - configManager: ConfigManager, mnemonic: Mnemonic, assetReader: AssetReader, moshi: Moshi, + configManager: ConfigManager, + blockchainDataStorage: BlockchainDataStorage, + accountCreator: AccountCreator, ) : WalletManagersFacade { private val demoConfig by lazy { DemoConfig() } private val resultFactory by lazy { UpdateWalletManagerResultFactory() } - private val walletManagerFactory by lazy { WalletManagerFactory(configManager) } + private val walletManagerFactory by lazy { + WalletManagerFactory( + configManager, + accountCreator, + blockchainDataStorage, + ) + } private val sdkTokenConverter by lazy { SdkTokenConverter() } private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() } private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) } + private val sdkPageConverter by lazy { SdkPageConverter() } private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory(mnemonic) } override suspend fun update( @@ -186,7 +196,7 @@ class DefaultWalletManagersFacade( override suspend fun getTxHistoryItems( userWalletId: UserWalletId, currency: CryptoCurrency, - page: Int, + page: Page, pageSize: Int, ): PaginationWrapper { val walletManager = getOrCreateWalletManager( @@ -202,7 +212,8 @@ class DefaultWalletManagersFacade( request = TransactionHistoryRequest( address = walletManager.wallet.address, decimals = currency.decimals, - page = TransactionHistoryRequest.Page(number = page, size = pageSize), + page = page, + pageSize = pageSize, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) @@ -212,9 +223,8 @@ class DefaultWalletManagersFacade( return when (itemsResult) { is Result.Success -> PaginationWrapper( - page = itemsResult.data.page, - totalPages = itemsResult.data.totalPages, - itemsOnPage = itemsResult.data.itemsOnPage, + currentPage = sdkPageConverter.convert(page), + nextPage = sdkPageConverter.convert(itemsResult.data.nextPage), items = txHistoryItemConverter.convertList(itemsResult.data.items), ) is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage) @@ -275,7 +285,11 @@ class DefaultWalletManagersFacade( resultFactory.getResult(walletManager) } catch (e: BlockchainSdkError.AccountNotFound) { - resultFactory.getNoAccountResult(walletManager = walletManager, customMessage = e.customMessage) + resultFactory.getNoAccountResult( + walletManager = walletManager, + customMessage = e.customMessage, + amountToCreateAccount = e.amountToCreateAccount, + ) } catch (e: Throwable) { Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}") @@ -287,7 +301,11 @@ class DefaultWalletManagersFacade( return try { resultFactory.getResult(walletManager) } catch (e: BlockchainSdkError.AccountNotFound) { - resultFactory.getNoAccountResult(walletManager = walletManager, customMessage = e.customMessage) + resultFactory.getNoAccountResult( + walletManager = walletManager, + customMessage = e.customMessage, + amountToCreateAccount = e.amountToCreateAccount, + ) } catch (e: Throwable) { Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}") @@ -321,6 +339,16 @@ class DefaultWalletManagersFacade( return walletManager } + @Deprecated("Will be removed in future") + override suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? { + val blockchain = Blockchain.fromId(network.id.value) + return getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = blockchain, + derivationPath = network.derivationPath.value, + ) + } + @Deprecated("Will be removed in future") override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List { return walletManagersStore.getAllSync(userWalletId) @@ -376,63 +404,6 @@ class DefaultWalletManagersFacade( } } - @Deprecated("Will be removed in future") - override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { - val manager = getOrCreateWalletManager( - userWalletId = userWalletId, - network = network, - ) - - return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null - } - - @Deprecated("Will be removed in future") - override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? { - val blockchain = Blockchain.fromId(network.id.value) - val manager = getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = blockchain, - derivationPath = network.derivationPath.value, - ) - - return manager?.dustValue - } - - @Deprecated("Will be removed in future") - override suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? { - val manager = getOrCreateWalletManager( - userWalletId = userWalletId, - network = network, - ) - - return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null - } - - @Deprecated("Will be removed in future") - override suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean { - val manager = getOrCreateWalletManager( - userWalletId = userWalletId, - network = network, - ) - - return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true - } - - @Deprecated("Will be removed in future") - override suspend fun checkUtxoAmountLimit( - userWalletId: UserWalletId, - network: Network, - amount: BigDecimal, - fee: BigDecimal, - ): UtxoAmountLimit? { - val manager = getOrCreateWalletManager( - userWalletId = userWalletId, - network = network, - ) - - return if (manager is UtxoAmountLimitProvider) manager.checkUtxoAmountLimit(amount, fee) else null - } - @Deprecated("Will be removed in future") override fun getAll(userWalletId: UserWalletId): Flow> { return walletManagersStore.getAll(userWalletId) @@ -605,13 +576,4 @@ class DefaultWalletManagersFacade( walletManager.addTokens(tokensToAdd) } - - private suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? { - val blockchain = Blockchain.fromId(network.id.value) - return getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = blockchain, - derivationPath = network.derivationPath.value, - ) - } } \ No newline at end of file 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 4988a715c5..543ef9006e 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 @@ -5,6 +5,7 @@ import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType +import com.tangem.blockchain.common.pagination.Page import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result @@ -97,7 +98,7 @@ interface WalletManagersFacade { suspend fun getTxHistoryItems( userWalletId: UserWalletId, currency: CryptoCurrency, - page: Int, + page: Page, pageSize: Int, ): PaginationWrapper @@ -108,6 +109,9 @@ interface WalletManagersFacade { derivationPath: String?, ): WalletManager? + @Deprecated("Will be removed in future") + suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? + @Deprecated("Will be removed in future") suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List @@ -136,47 +140,6 @@ interface WalletManagersFacade { */ suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? - /** - * Returns value which indicates if the account balance drops below the existential deposit value, it will be - * deactivated and any remaining funds will be destroyed. - * - * [REDACTED_TODO_COMMENT] - */ - @Deprecated("Will be removed in future") - suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? - - @Deprecated("Will be removed in future") - suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? - - /** - * Returns reserve amount which is required to create an account - * - * [REDACTED_TODO_COMMENT] - */ - @Deprecated("Will be removed in future") - suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? - - /** - * Returns true if account with [address] was reserved with minimum amount - * - * [REDACTED_TODO_COMMENT] - */ - @Deprecated("Will be removed in future") - suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean - - /** - * Checks if transaction amount is within the UTXO limit - * - * [REDACTED_TODO_COMMENT] - */ - @Deprecated("Will be removed in future") - suspend fun checkUtxoAmountLimit( - userWalletId: UserWalletId, - network: Network, - amount: BigDecimal, - fee: BigDecimal, - ): UtxoAmountLimit? - @Deprecated("Will be removed in future") fun getAll(userWalletId: UserWalletId): Flow> diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkPageConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkPageConverter.kt new file mode 100644 index 0000000000..f39c3bfd79 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkPageConverter.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.walletmanager.utils + +import com.tangem.domain.txhistory.models.Page +import com.tangem.utils.converter.TwoWayConverter +import com.tangem.blockchain.common.pagination.Page as SdkPage + +class SdkPageConverter : TwoWayConverter { + override fun convert(value: SdkPage): Page { + return when (value) { + is SdkPage.Initial -> Page.Initial + is SdkPage.LastPage -> Page.LastPage + is SdkPage.Next -> Page.Next(value = value.value) + } + } + + override fun convertBack(value: Page): SdkPage { + return when (value) { + is Page.Initial -> SdkPage.Initial + is Page.LastPage -> SdkPage.LastPage + is Page.Next -> SdkPage.Next(value = value.value) + } + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt index dbcc2c7ff5..6b88794b4b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt @@ -40,13 +40,17 @@ internal class UpdateWalletManagerResultFactory { ) } - fun getNoAccountResult(walletManager: WalletManager, customMessage: String): UpdateWalletManagerResult { + fun getNoAccountResult( + walletManager: WalletManager, + customMessage: String, + amountToCreateAccount: BigDecimal?, + ): UpdateWalletManagerResult { val wallet = walletManager.wallet val blockchain = wallet.blockchain val firstWalletToken = wallet.getTokens().firstOrNull() - val amountToCreateAccount = blockchain.amountToCreateAccount(firstWalletToken) + val amount = amountToCreateAccount ?: blockchain.amountToCreateAccount(firstWalletToken) - return if (amountToCreateAccount == null) { + return if (amount == null) { Timber.w("Unable to get required amount to create account for: $blockchain") UpdateWalletManagerResult.Unreachable( selectedAddress = wallet.address, @@ -56,7 +60,7 @@ internal class UpdateWalletManagerResultFactory { UpdateWalletManagerResult.NoAccount( selectedAddress = wallet.address, addresses = getAvailableAddresses(wallet.addresses), - amountToCreateAccount = amountToCreateAccount, + amountToCreateAccount = amount, errorMessage = customMessage, ) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt index 9e83e20e3a..e0377cf6a3 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt @@ -1,9 +1,10 @@ package com.tangem.domain.walletmanager.utils +import com.tangem.blockchain.common.AccountCreator import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.DerivationStyleProvider @@ -11,13 +12,16 @@ import com.tangem.domain.common.extensions.makeWalletManagerForApp import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.ScanResponse import timber.log.Timber +import com.tangem.blockchain.common.WalletManagerFactory as BlockchainWalletManagerFactory internal class WalletManagerFactory( - private val configManager: ConfigManager, + configManager: ConfigManager, + accountCreator: AccountCreator, + blockchainDataStorage: BlockchainDataStorage, ) { private val sdkWalletManagerFactory by lazy { - WalletManagerFactory(configManager.config.blockchainSdkConfig) + BlockchainWalletManagerFactory(configManager.config.blockchainSdkConfig, accountCreator, blockchainDataStorage) } fun createWalletManager( diff --git a/domain/tokens/models/build.gradle.kts b/domain/tokens/models/build.gradle.kts index f3bf4ce59c..948bad320b 100644 --- a/domain/tokens/models/build.gradle.kts +++ b/domain/tokens/models/build.gradle.kts @@ -16,4 +16,5 @@ dependencies { exclude(module = "joda-time") } implementation(deps.jodatime) + implementation(deps.timber) } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt index 70d50581d4..9b57fcfe15 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt @@ -1,5 +1,7 @@ package com.tangem.domain.tokens.model +import timber.log.Timber + /** * Represents a network address configuration. */ @@ -47,7 +49,9 @@ sealed class NetworkAddress { } init { - require(value.isNotBlank()) { "Address value must not be blank" } + if (value.isEmpty()) { + Timber.w("Address value is blank") + } } } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/blockchains/UtxoAmountLimit.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/blockchains/UtxoAmountLimit.kt new file mode 100644 index 0000000000..ef70be02c4 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/blockchains/UtxoAmountLimit.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.model.blockchains + +import java.math.BigDecimal + +/** + * Model stores utxo limits + * + * @property maxLimit utxo limit + * @property maxAmount max amount + */ +data class UtxoAmountLimit( + val maxLimit: BigDecimal, + val maxAmount: BigDecimal, +) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 794cf3a5f9..bbbc8c7071 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -2,10 +2,7 @@ package com.tangem.domain.tokens import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.exchange.RampStateManager -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.FeePaidCurrency -import com.tangem.domain.tokens.model.TokenActionsState +import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository @@ -94,17 +91,21 @@ class GetCryptoCurrencyActionsUseCase( return listOf(TokenActionsState.ActionState.HideToken(true)) } if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) { - return getActionsForUnreachableCurrency(cryptoCurrency) + return getActionsForUnreachableCurrency(cryptoCurrencyStatus) } val activeList = mutableListOf() val disabledList = mutableListOf() // copy address - activeList.add(TokenActionsState.ActionState.CopyAddress(true)) + if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { + activeList.add(TokenActionsState.ActionState.CopyAddress(true)) + } // receive - activeList.add(TokenActionsState.ActionState.Receive(true)) + if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { + activeList.add(TokenActionsState.ActionState.Receive(true)) + } // send if ( @@ -148,12 +149,16 @@ class GetCryptoCurrencyActionsUseCase( return activeList + disabledList } - private fun getActionsForUnreachableCurrency(cryptoCurrency: CryptoCurrency): List { + private fun getActionsForUnreachableCurrency( + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): List { val activeList = mutableListOf() val disabledList = mutableListOf() - activeList.add(TokenActionsState.ActionState.CopyAddress(true)) - if (rampManager.availableForBuy(cryptoCurrency)) { + if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { + activeList.add(TokenActionsState.ActionState.CopyAddress(true)) + } + if (rampManager.availableForBuy(cryptoCurrencyStatus.currency)) { activeList.add(TokenActionsState.ActionState.Buy(true)) } else { disabledList.add(TokenActionsState.ActionState.Buy(false)) @@ -161,7 +166,10 @@ class GetCryptoCurrencyActionsUseCase( disabledList.add(TokenActionsState.ActionState.Send(false)) disabledList.add(TokenActionsState.ActionState.Swap(false)) disabledList.add(TokenActionsState.ActionState.Sell(false)) - activeList.add(TokenActionsState.ActionState.Receive(true)) + + if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { + activeList.add(TokenActionsState.ActionState.Receive(true)) + } activeList.add(TokenActionsState.ActionState.HideToken(true)) return activeList + disabledList } @@ -200,6 +208,10 @@ class GetCryptoCurrencyActionsUseCase( } } + private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean { + return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty() + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index f8f9049833..6e18152f25 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -29,6 +29,7 @@ class GetCurrencyWarningsUseCase( private val promoRepository: PromoRepository, private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, private val dispatchers: CoroutineDispatcherProvider, + private val currencyChecksRepository: CurrencyChecksRepository, ) { suspend operator fun invoke( @@ -54,7 +55,7 @@ class GetCurrencyWarningsUseCase( isSingleWalletWithTokens = isSingleWalletWithTokens, ), flowOf(walletManagersFacade.getRentInfo(userWalletId, currency.network)), - flowOf(walletManagersFacade.getExistentialDeposit(userWalletId, currency.network)), + flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), getSwapPromoNotificationWarning( operations = operations, userWalletId = userWalletId, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ValidateContractAddressUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ValidateContractAddressUseCase.kt index 361a79c45c..a2c2d2c1b4 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ValidateContractAddressUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ValidateContractAddressUseCase.kt @@ -3,22 +3,28 @@ package com.tangem.domain.tokens import arrow.core.Either import arrow.core.raise.catch import arrow.core.raise.either -import com.tangem.domain.AddCustomTokenError +import com.tangem.domain.tokens.error.AddCustomTokenError import com.tangem.domain.tokens.repository.TokensListRepository -class ValidateContractAddressUseCase(private val tokensListRepository: TokensListRepository) { +class ValidateContractAddressUseCase( + private val tokensListRepository: TokensListRepository, +) { operator fun invoke(address: String, networkId: String): Either { return either { catch( block = { - if (address.isEmpty()) raise(AddCustomTokenError.FieldIsEmpty) + if (address.isEmpty()) raise(AddCustomTokenError.FIELD_IS_EMPTY) - if (!tokensListRepository.validateAddress(networkId, address)) { - raise(AddCustomTokenError.InvalidContractAddress) + if (!tokensListRepository.validateAddress( + contractAddress = address, + networkId = networkId, + ) + ) { + raise(AddCustomTokenError.INVALID_CONTRACT_ADDRESS) } }, catch = { - AddCustomTokenError.InvalidContractAddress + AddCustomTokenError.INVALID_CONTRACT_ADDRESS }, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/AddCustomTokenError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/AddCustomTokenError.kt new file mode 100644 index 0000000000..fcbb456b66 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/AddCustomTokenError.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.tokens.error + +enum class AddCustomTokenError { + FIELD_IS_EMPTY, + INVALID_CONTRACT_ADDRESS, +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt new file mode 100644 index 0000000000..914007040a --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -0,0 +1,32 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit +import com.tangem.domain.wallets.models.UserWalletId +import java.math.BigDecimal + +interface CurrencyChecksRepository { + + /** + * Returns value which indicates if the account balance drops below the existential deposit value, it will be + * deactivated and any remaining funds will be destroyed. + */ + suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? + + /** Returns dust value */ + suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? + + /** Returns reserve amount which is required to create an account */ + suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? + + /** Returns true if account with [address] was reserved with minimum amount */ + suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean + + /** Checks if transaction amount is within the UTXO limit */ + suspend fun checkUtxoAmountLimit( + userWalletId: UserWalletId, + network: Network, + amount: BigDecimal, + fee: BigDecimal, + ): UtxoAmountLimit? +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt index 2d5536cefb..753f45e4b4 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt @@ -14,6 +14,8 @@ sealed class SendTransactionError { object UserCancelledError : SendTransactionError() + data class CreateAccountUnderfunded(val amount: String) : SendTransactionError() + data class TangemSdkError(val code: Int, val messageReference: TextReference) : SendTransactionError() data class UnknownError(val ex: Exception? = null) : SendTransactionError() diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index c8ed1fe905..059a672c70 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -20,6 +20,7 @@ import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE import com.tangem.domain.wallets.models.UserWallet import com.tangem.sdk.extensions.localizedDescriptionRes +import com.tangem.utils.toFormattedString class SendTransactionUseCase( private val isDemoCardUseCase: IsDemoCardUseCase, @@ -85,6 +86,11 @@ class SendTransactionUseCase( } } } + is BlockchainSdkError.CreateAccountUnderfunded -> { + val minAmount = error.minReserve + val minValue = minAmount.value?.toFormattedString(minAmount.decimals).orEmpty() + SendTransactionError.CreateAccountUnderfunded(minValue) + } else -> { SendTransactionError.BlockchainSdkError( code = error.code, diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt new file mode 100644 index 0000000000..9e18e15403 --- /dev/null +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/Page.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.txhistory.models + +sealed class Page { + object Initial : Page() + data class Next(val value: String) : Page() + object LastPage : Page() +} \ No newline at end of file diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/PaginationWrapper.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/PaginationWrapper.kt index 92ea34de36..71cf94bb8b 100644 --- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/PaginationWrapper.kt +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/PaginationWrapper.kt @@ -1,8 +1,7 @@ package com.tangem.domain.txhistory.models data class PaginationWrapper( - val page: Int, - val totalPages: Int, - val itemsOnPage: Int, + val currentPage: Page, + val nextPage: Page, val items: List, ) \ No newline at end of file diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 64262e878a..8032c2e995 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -19,4 +19,5 @@ dependencies { /** Libs - Other */ implementation(deps.jodatime) + implementation(deps.androidx.paging.runtime) } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxDetailsUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxDetailsUseCase.kt new file mode 100644 index 0000000000..3ff945017f --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxDetailsUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.visa + +import arrow.core.Either +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.domain.visa.repository.VisaRepository +import com.tangem.domain.wallets.models.UserWalletId + +class GetVisaTxDetailsUseCase( + private val visaRepository: VisaRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, transactionId: String): Either { + return Either.catch { visaRepository.getTxDetails(userWalletId, transactionId) } + } +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxHistoryUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxHistoryUseCase.kt new file mode 100644 index 0000000000..c14447313e --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/GetVisaTxHistoryUseCase.kt @@ -0,0 +1,32 @@ +package com.tangem.domain.visa + +import androidx.paging.PagingData +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.domain.visa.repository.VisaRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +class GetVisaTxHistoryUseCase( + private val visaRepository: VisaRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + pageSize: Int = DEFAULT_PAGE_SIZE, + isRefresh: Boolean = false, + ): Flow>> { + return visaRepository + .getTxHistory(userWalletId, pageSize, isRefresh) + .map, Either>> { it.right() } + .catch { emit(it.left()) } + } + + companion object { + const val DEFAULT_PAGE_SIZE = 10 + } +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxDetails.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxDetails.kt new file mode 100644 index 0000000000..1e833b7c16 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxDetails.kt @@ -0,0 +1,40 @@ +package com.tangem.domain.visa.model + +import org.joda.time.DateTime +import java.math.BigDecimal +import java.util.Currency + +data class VisaTxDetails( + val id: String, + val type: String, + val status: String, + val blockchainAmount: BigDecimal, + val blockchainFee: BigDecimal, + val transactionAmount: BigDecimal, + val transactionCurrencyCode: Int, + val merchantName: String?, + val merchantCity: String?, + val merchantCountryCode: String?, + val merchantCategoryCode: String?, + val fiatCurrency: Currency, + val requests: List, +) { + + data class Request( + val id: String, + val billingAmount: BigDecimal, + val billingCurrencyCode: Int, + val blockchainAmount: BigDecimal, + val blockchainFee: BigDecimal, + val errorCode: Int, + val requestDate: DateTime, + val requestStatus: String, + val requestType: String, + val transactionAmount: BigDecimal, + val txCurrencyCode: Int, + val txHash: String?, + val txStatus: String?, + val exploreUrl: String?, + val fiatCurrency: Currency, + ) +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxHistoryItem.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxHistoryItem.kt new file mode 100644 index 0000000000..6d3bcc876a --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaTxHistoryItem.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.visa.model + +import org.joda.time.DateTime +import java.math.BigDecimal +import java.util.Currency + +data class VisaTxHistoryItem( + val id: String, + val date: DateTime, + val amount: BigDecimal, + val fiatAmount: BigDecimal, + val merchantName: String?, + val status: String, + val fiatCurrency: Currency, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaRepository.kt index 96d6efd7fd..dac5f4cf72 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaRepository.kt @@ -1,9 +1,21 @@ package com.tangem.domain.visa.repository +import androidx.paging.PagingData import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow interface VisaRepository { suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean = false): VisaCurrency + + suspend fun getTxHistory( + userWalletId: UserWalletId, + pageSize: Int, + isRefresh: Boolean = false, + ): Flow> + + suspend fun getTxDetails(userWalletId: UserWalletId, txId: String): VisaTxDetails } \ No newline at end of file diff --git a/features/manage-tokens/api/build.gradle.kts b/features/manage-tokens/api/build.gradle.kts index aa4ad2bd26..cbfb3f7b1f 100644 --- a/features/manage-tokens/api/build.gradle.kts +++ b/features/manage-tokens/api/build.gradle.kts @@ -9,6 +9,5 @@ android { } dependencies { - /** AndroidX */ - implementation(deps.androidx.fragment.ktx) + implementation(deps.compose.foundation) } \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt deleted file mode 100644 index 7dd58d2a5b..0000000000 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.managetokens.navigation - -import androidx.fragment.app.Fragment - -interface ManageTokensRouter { - - fun getEntryFragment(): Fragment -} \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt new file mode 100644 index 0000000000..7e5c95da12 --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt @@ -0,0 +1,10 @@ +package com.tangem.features.managetokens.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp + +interface ManageTokensUi { + @Suppress("TopLevelComposableFunctions") + @Composable + fun Content(onHeaderSizeChange: (Dp) -> Unit) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts index 692109ece2..8fb983d6d7 100644 --- a/features/manage-tokens/impl/build.gradle.kts +++ b/features/manage-tokens/impl/build.gradle.kts @@ -35,12 +35,15 @@ dependencies { implementation(deps.jodatime) implementation(deps.kotlin.immutable.collections) implementation(deps.tangem.card.core) + implementation(deps.timber) /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) /** Core modules */ + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) implementation(projects.core.featuretoggles) implementation(projects.core.navigation) implementation(projects.core.ui) @@ -57,8 +60,6 @@ dependencies { implementation(projects.domain.settings) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) - implementation(projects.domain.txhistory) - implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.appCurrency) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/ManageTokensFragment.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/ManageTokensFragment.kt deleted file mode 100644 index 87b216af73..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/ManageTokensFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.managetokens - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder -import com.tangem.features.managetokens.navigation.ManageTokensRouter -import com.tangem.managetokens.presentation.router.InnerManageTokensRouter -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -internal class ManageTokensFragment : ComposeFragment() { - - @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - lateinit var manageTokensRouter: ManageTokensRouter - - private val innerManageTokensRouter: InnerManageTokensRouter - get() = requireNotNull(manageTokensRouter as? InnerManageTokensRouter) { - "internalManageTokensRouter should be instance of InnerManageTokensRouter" - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - val systemBarsColor = TangemTheme.colors.background.secondary - SystemBarsEffect { - setSystemBarsColor(systemBarsColor) - } - - innerManageTokensRouter.Initialize(viewModelStoreOwner = this) - } -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt index 12c7db4c53..e0ca63c3b2 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt @@ -1,21 +1,18 @@ package com.tangem.managetokens.di -import com.tangem.core.navigation.ReduxNavController -import com.tangem.features.managetokens.navigation.ManageTokensRouter -import com.tangem.managetokens.presentation.router.DefaultManageTokensRouter +import com.tangem.features.managetokens.navigation.ManageTokensUi +import com.tangem.managetokens.presentation.router.ManageTokensUiImpl +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton @Module -@InstallIn(ActivityComponent::class) -internal object ManageTokensRouterModule { +@InstallIn(SingletonComponent::class) +internal interface ManageTokensRouterModule { - @Provides - @ActivityScoped - fun provideManageTokensRouter(reduxNavController: ReduxNavController): ManageTokensRouter { - return DefaultManageTokensRouter(reduxNavController) - } + @Binds + @Singleton + fun provideManageTokensRouter(manageTokensUiImpl: ManageTokensUiImpl): ManageTokensUi } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt new file mode 100644 index 0000000000..fad644416a --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt @@ -0,0 +1,14 @@ +package com.tangem.managetokens.presentation.addcustomtoken.router + +/** + * Add Custom Tokens screens + * @property route route string representation + */ +internal sealed class AddCustomTokenRoute(val route: String) { + object Main : AddCustomTokenRoute("$BASE_ROUTE/main") + object ChooseNetwork : AddCustomTokenRoute("$BASE_ROUTE/choose_network") + object ChooseWallet : AddCustomTokenRoute("$BASE_ROUTE/choose_wallet") + object ChooseDerivation : AddCustomTokenRoute("$BASE_ROUTE/choose_derivation") +} + +private const val BASE_ROUTE = "manage_tokens/add_custom_token" \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt new file mode 100644 index 0000000000..4185ccddd4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt @@ -0,0 +1,29 @@ +package com.tangem.managetokens.presentation.addcustomtoken.router + +import androidx.compose.runtime.Stable +import androidx.navigation.NavController + +@Stable +internal class AddCustomTokenRouter( + private val navController: NavController, +) { + /** Pop back stack */ + fun popBackStack() { + navController.popBackStack() + } + + /** Open custom token choose network screen */ + fun openCustomTokenChooseNetwork() { + navController.navigate(AddCustomTokenRoute.ChooseNetwork.route) + } + + /** Open custom token choose derivation screen */ + fun openCustomTokenChooseDerivation() { + navController.navigate(AddCustomTokenRoute.ChooseDerivation.route) + } + + /** Open custom token choose wallet screen */ + fun openCustomTokenChooseWallet() { + navController.navigate(AddCustomTokenRoute.ChooseWallet.route) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenState.kt similarity index 91% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenState.kt index 5c37331d11..0eafc00654 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.consumedEvent diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenWarning.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenWarning.kt similarity index 94% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenWarning.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenWarning.kt index fdca691ce8..690178c20d 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/AddCustomTokenWarning.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/AddCustomTokenWarning.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ButtonState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ButtonState.kt similarity index 58% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ButtonState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ButtonState.kt index 0b1412cc0b..42b515ea20 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ButtonState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ButtonState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state internal data class ButtonState( val isEnabled: Boolean, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseDerivationState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseDerivationState.kt similarity index 86% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseDerivationState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseDerivationState.kt index c9bb1eefc4..047fae161d 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseDerivationState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseDerivationState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state import kotlinx.collections.immutable.ImmutableList diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseNetworkState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseNetworkState.kt similarity index 85% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseNetworkState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseNetworkState.kt index 1a09c93ec3..56b993010b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/ChooseNetworkState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/ChooseNetworkState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state import com.tangem.managetokens.presentation.common.state.NetworkItemState import kotlinx.collections.immutable.ImmutableList diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/CustomTokenData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/CustomTokenData.kt similarity index 65% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/CustomTokenData.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/CustomTokenData.kt index a586ec848a..b481d6767a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/CustomTokenData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/CustomTokenData.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state internal data class CustomTokenData( val contractAddressTextField: TextFieldState, @@ -11,4 +11,8 @@ internal data class CustomTokenData( return contractAddressTextField.isInputValid() && nameTextField.isInputValid() && symbolTextField.isInputValid() && decimalsTextField.isInputValid() } + + fun isNameSymbolDecimalsDisabled(): Boolean { + return nameTextField.isDisabled() && symbolTextField.isDisabled() && decimalsTextField.isDisabled() + } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/Derivation.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/Derivation.kt similarity index 74% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/Derivation.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/Derivation.kt index cb06fa67b7..69f41a5f08 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/Derivation.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/Derivation.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state internal data class Derivation( val networkName: String, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/EnterCustomDerivationState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/EnterCustomDerivationState.kt similarity index 80% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/EnterCustomDerivationState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/EnterCustomDerivationState.kt index aefe21b295..3b6ca1c57a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/EnterCustomDerivationState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/EnterCustomDerivationState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state internal data class EnterCustomDerivationState( val value: String, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/TextFieldState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/TextFieldState.kt similarity index 85% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/TextFieldState.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/TextFieldState.kt index 090d4a2a22..3068a0b6e9 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/TextFieldState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/TextFieldState.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state +package com.tangem.managetokens.presentation.addcustomtoken.state internal sealed class TextFieldState { object Loading : TextFieldState() @@ -8,10 +8,13 @@ internal sealed class TextFieldState { val isEnabled: Boolean, val error: AddCustomTokenWarning? = null, val onValueChange: (String) -> Unit, + val onFocusExit: () -> Unit, ) : TextFieldState() fun isInputValid(): Boolean = this is Editable && value.isNotBlank() && error == null + fun isDisabled() = this is Editable && !this.isEnabled + fun copySealed( value: String = (this as? Editable)?.value ?: "", isEnabled: Boolean = (this as? Editable)?.isEnabled ?: true, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/CustomTokensStateFactory.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateFactory.kt similarity index 85% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/CustomTokensStateFactory.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateFactory.kt index 8a72ab97bf..7fe3b1dc8c 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/CustomTokensStateFactory.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateFactory.kt @@ -1,23 +1,23 @@ -package com.tangem.managetokens.presentation.customtokens.state.factory +package com.tangem.managetokens.presentation.addcustomtoken.state.factory import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.domain.AddCustomTokenError +import com.tangem.domain.tokens.error.AddCustomTokenError import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.managetokens.presentation.common.state.* -import com.tangem.managetokens.presentation.customtokens.state.* -import com.tangem.managetokens.presentation.customtokens.viewmodels.CustomTokensClickIntents +import com.tangem.managetokens.presentation.addcustomtoken.state.* +import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet -internal class CustomTokensStateFactory( +internal class AddCustomTokenStateFactory( private val currentStateProvider: Provider, - private val clickIntents: CustomTokensClickIntents, + private val clickIntents: AddCustomTokenClickIntents, ) { fun getInitialState(): AddCustomTokenState { @@ -176,6 +176,32 @@ internal class CustomTokensStateFactory( return currentStateProvider().copy(tokenData = tokenData.copy(contractAddressTextField = contractAddressField)) } + private fun unlockAndClearNameSymbolAndDecimals(state: AddCustomTokenState): AddCustomTokenState { + val currentTokenData = state.tokenData + return state.copy( + tokenData = currentTokenData?.copy( + nameTextField = TextFieldState.Editable( + value = "", + isEnabled = true, + onValueChange = clickIntents::onTokenNameChange, + onFocusExit = clickIntents::onTokenNameFocusExit, + ), + symbolTextField = TextFieldState.Editable( + value = "", + isEnabled = true, + onValueChange = clickIntents::onSymbolChange, + onFocusExit = clickIntents::onSymbolFocusExit, + ), + decimalsTextField = TextFieldState.Editable( + value = "", + isEnabled = true, + onValueChange = clickIntents::onDecimalsChange, + onFocusExit = clickIntents::onDecimalsFocusExit, + ), + ), + ) + } + fun getStateAndTriggerEvent( state: AddCustomTokenState, event: Event, @@ -201,21 +227,25 @@ internal class CustomTokensStateFactory( value = "", isEnabled = true, onValueChange = clickIntents::onContractAddressChange, + onFocusExit = clickIntents::onContractAddressFocusExit, ), nameTextField = TextFieldState.Editable( value = "", isEnabled = false, onValueChange = clickIntents::onTokenNameChange, + onFocusExit = clickIntents::onTokenNameFocusExit, ), symbolTextField = TextFieldState.Editable( value = "", isEnabled = false, onValueChange = clickIntents::onSymbolChange, + onFocusExit = clickIntents::onSymbolFocusExit, ), decimalsTextField = TextFieldState.Editable( value = "", isEnabled = false, onValueChange = clickIntents::onDecimalsChange, + onFocusExit = clickIntents::onDecimalsFocusExit, ), ) } else { @@ -268,6 +298,8 @@ internal class CustomTokensStateFactory( value = contractAddress, isEnabled = true, onValueChange = clickIntents::onContractAddressChange, + onFocusExit = clickIntents::onContractAddressFocusExit, + ), nameTextField = TextFieldState.Loading, symbolTextField = TextFieldState.Loading, @@ -279,8 +311,8 @@ internal class CustomTokensStateFactory( fun handleAddressError(error: AddCustomTokenError): AddCustomTokenState { val uiState = currentStateProvider() return when (error) { - AddCustomTokenError.InvalidContractAddress -> { - addTokenAddressFieldError(AddCustomTokenWarning.InvalidContractAddress) + AddCustomTokenError.INVALID_CONTRACT_ADDRESS -> { + removeTokenAddressError().also { unlockAndClearNameSymbolAndDecimals(it) } .copy( addTokenButton = uiState.addTokenButton.copy(isEnabled = false), warnings = uiState.warnings @@ -288,8 +320,8 @@ internal class CustomTokensStateFactory( .toPersistentSet(), ) } - AddCustomTokenError.FieldIsEmpty -> - removeTokenAddressError() + AddCustomTokenError.FIELD_IS_EMPTY -> + removeTokenAddressError().also { unlockAndClearNameSymbolAndDecimals(it) } .copy( addTokenButton = uiState.addTokenButton.copy( isEnabled = uiState.tokenData?.isRequiredInformationProvided() == true, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt similarity index 84% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt index 94f5828c6c..e6e1b2f84e 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/AddCustomTokenStateToCryptoCurrencyConverter.kt @@ -1,11 +1,11 @@ -package com.tangem.managetokens.presentation.customtokens.state.factory +package com.tangem.managetokens.presentation.addcustomtoken.state.factory import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.managetokens.presentation.customtokens.state.AddCustomTokenState -import com.tangem.managetokens.presentation.customtokens.state.CustomTokenData -import com.tangem.managetokens.presentation.customtokens.state.TextFieldState +import com.tangem.managetokens.presentation.addcustomtoken.state.AddCustomTokenState +import com.tangem.managetokens.presentation.addcustomtoken.state.CustomTokenData +import com.tangem.managetokens.presentation.addcustomtoken.state.TextFieldState import com.tangem.utils.converter.Converter internal class AddCustomTokenStateToCryptoCurrencyConverter( @@ -39,7 +39,7 @@ internal class AddCustomTokenStateToCryptoCurrencyConverter( private fun parseTokenOrNull(tokenData: CustomTokenData?): CryptoCurrencyFactory.Token? { val contractAddress = (tokenData?.contractAddressTextField as? TextFieldState.Editable)?.value val symbol = (tokenData?.symbolTextField as? TextFieldState.Editable)?.value - val name = (tokenData?.contractAddressTextField as? TextFieldState.Editable)?.value + val name = (tokenData?.nameTextField as? TextFieldState.Editable)?.value val decimals = (tokenData?.decimalsTextField as? TextFieldState.Editable)?.value?.toIntOrNull() return if ( !contractAddress.isNullOrBlank() && !symbol.isNullOrBlank() && !name.isNullOrBlank() && decimals != null diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/ContractAddressToCustomTokenDataConverter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/ContractAddressToCustomTokenDataConverter.kt similarity index 61% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/ContractAddressToCustomTokenDataConverter.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/ContractAddressToCustomTokenDataConverter.kt index b6ab812200..eb0897d370 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/ContractAddressToCustomTokenDataConverter.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/ContractAddressToCustomTokenDataConverter.kt @@ -1,12 +1,12 @@ -package com.tangem.managetokens.presentation.customtokens.state.factory +package com.tangem.managetokens.presentation.addcustomtoken.state.factory -import com.tangem.managetokens.presentation.customtokens.state.CustomTokenData -import com.tangem.managetokens.presentation.customtokens.state.TextFieldState -import com.tangem.managetokens.presentation.customtokens.viewmodels.CustomTokensClickIntents +import com.tangem.managetokens.presentation.addcustomtoken.state.CustomTokenData +import com.tangem.managetokens.presentation.addcustomtoken.state.TextFieldState +import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenClickIntents import com.tangem.utils.converter.Converter internal class ContractAddressToCustomTokenDataConverter( - private val clickIntents: CustomTokensClickIntents, + private val clickIntents: AddCustomTokenClickIntents, ) : Converter { override fun convert(value: String): CustomTokenData { return CustomTokenData( @@ -14,21 +14,25 @@ internal class ContractAddressToCustomTokenDataConverter( value = value, isEnabled = true, onValueChange = clickIntents::onContractAddressChange, + onFocusExit = clickIntents::onContractAddressFocusExit, ), nameTextField = TextFieldState.Editable( value = "", isEnabled = true, onValueChange = clickIntents::onTokenNameChange, + onFocusExit = clickIntents::onTokenNameFocusExit, ), symbolTextField = TextFieldState.Editable( value = "", isEnabled = true, onValueChange = clickIntents::onSymbolChange, + onFocusExit = clickIntents::onSymbolFocusExit, ), decimalsTextField = TextFieldState.Editable( value = "", isEnabled = true, onValueChange = clickIntents::onDecimalsChange, + onFocusExit = clickIntents::onDecimalsFocusExit, ), ) } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/FoundTokenToCustomTokenDataConverter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/FoundTokenToCustomTokenDataConverter.kt similarity index 64% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/FoundTokenToCustomTokenDataConverter.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/FoundTokenToCustomTokenDataConverter.kt index ebe6b1bee2..65921700dc 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/FoundTokenToCustomTokenDataConverter.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/FoundTokenToCustomTokenDataConverter.kt @@ -1,13 +1,13 @@ -package com.tangem.managetokens.presentation.customtokens.state.factory +package com.tangem.managetokens.presentation.addcustomtoken.state.factory import com.tangem.domain.tokens.model.FoundToken -import com.tangem.managetokens.presentation.customtokens.state.CustomTokenData -import com.tangem.managetokens.presentation.customtokens.state.TextFieldState -import com.tangem.managetokens.presentation.customtokens.viewmodels.CustomTokensClickIntents +import com.tangem.managetokens.presentation.addcustomtoken.state.CustomTokenData +import com.tangem.managetokens.presentation.addcustomtoken.state.TextFieldState +import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenClickIntents import com.tangem.utils.converter.Converter internal class FoundTokenToCustomTokenDataConverter( - private val clickIntents: CustomTokensClickIntents, + private val clickIntents: AddCustomTokenClickIntents, ) : Converter { override fun convert(value: FoundToken): CustomTokenData { return CustomTokenData( @@ -15,21 +15,25 @@ internal class FoundTokenToCustomTokenDataConverter( value = value.contractAddress, isEnabled = true, onValueChange = clickIntents::onContractAddressChange, + onFocusExit = clickIntents::onContractAddressFocusExit, ), nameTextField = TextFieldState.Editable( value = value.name, isEnabled = false, onValueChange = clickIntents::onTokenNameChange, + onFocusExit = clickIntents::onTokenNameFocusExit, ), symbolTextField = TextFieldState.Editable( value = value.symbol, isEnabled = false, onValueChange = clickIntents::onSymbolChange, + onFocusExit = clickIntents::onSymbolFocusExit, ), decimalsTextField = TextFieldState.Editable( value = value.decimals.toString(), isEnabled = false, onValueChange = clickIntents::onDecimalsChange, + onFocusExit = clickIntents::onDecimalsFocusExit, ), ) } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/NetworkToNetworkItemStateConverter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/NetworkToNetworkItemStateConverter.kt similarity index 91% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/NetworkToNetworkItemStateConverter.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/NetworkToNetworkItemStateConverter.kt index dee07b42c5..74b4eda971 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/factory/NetworkToNetworkItemStateConverter.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/factory/NetworkToNetworkItemStateConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.state.factory +package com.tangem.managetokens.presentation.addcustomtoken.state.factory import com.tangem.core.ui.extensions.getActiveIconResByNetworkId import com.tangem.domain.tokens.model.Network diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/AddCustomTokenPreviewData.kt similarity index 90% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/AddCustomTokenPreviewData.kt index 998c00a3e7..96e7bf7379 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/AddCustomTokenPreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/AddCustomTokenPreviewData.kt @@ -1,8 +1,8 @@ -package com.tangem.managetokens.presentation.customtokens.state.previewdata +package com.tangem.managetokens.presentation.addcustomtoken.state.previewdata import com.tangem.managetokens.presentation.common.state.ChooseWalletState import com.tangem.managetokens.presentation.common.state.WalletState -import com.tangem.managetokens.presentation.customtokens.state.* +import com.tangem.managetokens.presentation.addcustomtoken.state.* import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf @@ -38,6 +38,7 @@ internal object AddCustomTokenPreviewData { value = "0x4ace7262705b68bcba5b91de96889349394", isEnabled = false, onValueChange = {}, + onFocusExit = {}, ), nameTextField = TextFieldState.Loading, symbolTextField = TextFieldState.Loading, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseDerivationPreviewData.kt similarity index 83% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseDerivationPreviewData.kt index 2bb35400d2..c4cfd15483 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseDerivationPreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseDerivationPreviewData.kt @@ -1,7 +1,7 @@ -package com.tangem.managetokens.presentation.customtokens.state.previewdata +package com.tangem.managetokens.presentation.addcustomtoken.state.previewdata -import com.tangem.managetokens.presentation.customtokens.state.ChooseDerivationState -import com.tangem.managetokens.presentation.customtokens.state.Derivation +import com.tangem.managetokens.presentation.addcustomtoken.state.ChooseDerivationState +import com.tangem.managetokens.presentation.addcustomtoken.state.Derivation import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseNetworkCustomPreviewData.kt similarity index 85% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseNetworkCustomPreviewData.kt index 5618ce7fb4..08987dbadf 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/state/previewdata/ChooseNetworkCustomPreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/state/previewdata/ChooseNetworkCustomPreviewData.kt @@ -1,8 +1,8 @@ -package com.tangem.managetokens.presentation.customtokens.state.previewdata +package com.tangem.managetokens.presentation.addcustomtoken.state.previewdata import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.state.NetworkItemState -import com.tangem.managetokens.presentation.customtokens.state.ChooseNetworkState +import com.tangem.managetokens.presentation.addcustomtoken.state.ChooseNetworkState import kotlinx.collections.immutable.persistentListOf internal object ChooseNetworkCustomPreviewData { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt new file mode 100644 index 0000000000..499c2e6cb4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt @@ -0,0 +1,115 @@ +package com.tangem.managetokens.presentation.addcustomtoken.ui + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetDraggableHeader +import com.tangem.core.ui.components.bottomsheets.collapse +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRoute +import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRouter +import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenViewModel +import com.tangem.managetokens.presentation.common.state.ChooseWalletState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddCustomTokenBottomSheet(config: TangemBottomSheetConfig) { + val viewModel = hiltViewModel() + + var isVisible by remember { mutableStateOf(value = config.isShow) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + if (isVisible) { + // ViewModel cannot be scoped to ModalBottomSheet's lifecycle, + // so we have to manually initialize and dispose its state when bottom sheet enters and leaves a composition + DisposableEffect(viewModel) { + viewModel.onInitialize() + onDispose { viewModel.onDispose() } + } + + // FIXME: handle back presses after updating material3 to 1.2.0 + ModalBottomSheet( + onDismissRequest = config.onDismissRequest, + sheetState = sheetState, + containerColor = TangemTheme.colors.background.tertiary, + shape = TangemTheme.shapes.bottomSheetLarge, + windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), + dragHandle = { TangemBottomSheetDraggableHeader(color = TangemTheme.colors.background.tertiary) }, + ) { + Content(onDismissRequest = config.onDismissRequest, viewModel = viewModel) + } + } + + LaunchedEffect(key1 = config.isShow) { + if (config.isShow) { + isVisible = true + } else { + sheetState.collapse { isVisible = false } + } + } +} + +@SuppressLint("RestrictedApi") +@Composable +private fun Content(viewModel: AddCustomTokenViewModel, onDismissRequest: () -> Unit) { + val navController = rememberNavController() + + LaunchedEffect(navController) { + navController.currentBackStack + .collect { + if (it.isEmpty()) { + onDismissRequest() + } + } + } + + val router = remember(navController) { AddCustomTokenRouter(navController) } + + viewModel.router = router + + NavHost( + modifier = Modifier.fillMaxSize(), + navController = navController, + startDestination = AddCustomTokenRoute.Main.route, + ) { + composable( + route = AddCustomTokenRoute.Main.route, + ) { + AddCustomTokenScreen(state = viewModel.uiState) + } + composable( + route = AddCustomTokenRoute.ChooseNetwork.route, + ) { + ChooseNetworkCustomScreen(state = viewModel.uiState.chooseNetworkState) + } + composable( + route = AddCustomTokenRoute.ChooseDerivation.route, + ) { + ChooseDerivationScreen(state = requireNotNull(viewModel.uiState.chooseDerivationState)) + } + composable( + route = AddCustomTokenRoute.ChooseWallet.route, + ) { + CustomTokensChooseWalletScreen(state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose) + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt similarity index 74% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt index 98f97905a6..d335aaca71 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt @@ -1,20 +1,27 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.* +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R @@ -25,13 +32,13 @@ import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheetCon import com.tangem.managetokens.presentation.common.ui.EventEffect import com.tangem.managetokens.presentation.common.ui.components.Alert import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock -import com.tangem.managetokens.presentation.customtokens.state.AddCustomTokenState -import com.tangem.managetokens.presentation.customtokens.state.CustomTokenData -import com.tangem.managetokens.presentation.customtokens.state.TextFieldState -import com.tangem.managetokens.presentation.customtokens.state.previewdata.AddCustomTokenPreviewData +import com.tangem.managetokens.presentation.addcustomtoken.state.AddCustomTokenState +import com.tangem.managetokens.presentation.addcustomtoken.state.CustomTokenData +import com.tangem.managetokens.presentation.addcustomtoken.state.TextFieldState +import com.tangem.managetokens.presentation.addcustomtoken.state.previewdata.AddCustomTokenPreviewData @Composable -internal fun CustomTokensScreen(state: AddCustomTokenState, modifier: Modifier = Modifier) { +internal fun AddCustomTokenScreen(state: AddCustomTokenState, modifier: Modifier = Modifier) { var alertState by remember { mutableStateOf(value = null) } EventEffect( @@ -46,11 +53,14 @@ internal fun CustomTokensScreen(state: AddCustomTokenState, modifier: Modifier = @Composable private fun Content(state: AddCustomTokenState, modifier: Modifier = Modifier) { + val keyboard by keyboardAsState() + Column( modifier = modifier .background(color = TangemTheme.colors.background.tertiary) .statusBarsPadding() .navigationBarsPadding() + .imePadding() .padding( top = TangemTheme.dimens.spacing10, start = TangemTheme.dimens.spacing16, @@ -78,12 +88,14 @@ private fun Content(state: AddCustomTokenState, modifier: Modifier = Modifier) { .padding(bottom = TangemTheme.dimens.spacing16), ) CustomTokenItemsList(state = state) - PrimaryButton( - text = stringResource(id = R.string.custom_token_add_token), - onClick = state.addTokenButton.onClick, - enabled = state.addTokenButton.isEnabled, - modifier = Modifier.fillMaxWidth(), - ) + if (keyboard is Keyboard.Closed) { + PrimaryButton( + text = stringResource(id = R.string.custom_token_add_token), + onClick = state.addTokenButton.onClick, + enabled = state.addTokenButton.isEnabled, + modifier = Modifier.fillMaxWidth(), + ) + } } if (state.chooseWalletState is ChooseWalletState.Choose && state.chooseWalletState.show) { @@ -147,8 +159,11 @@ private fun ColumnScope.CustomTokenItemsList(state: AddCustomTokenState, modifie } } +@OptIn(ExperimentalComposeUiApi::class) @Composable private fun TokenFields(state: CustomTokenData, modifier: Modifier = Modifier) { + val focusManager = LocalFocusManager.current + Column( modifier = modifier .fillMaxWidth() @@ -159,21 +174,51 @@ private fun TokenFields(state: CustomTokenData, modifier: Modifier = Modifier) { textFieldState = state.contractAddressTextField, placeholder = "0x0000000000000000000000000000000", title = R.string.custom_token_contract_address_input_title, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = if (state.isNameSymbolDecimalsDisabled()) { + ImeAction.Done + } else { + ImeAction.Next + }, + ), + onImeAction = { + if (state.isNameSymbolDecimalsDisabled()) { + focusManager.moveFocus(FocusDirection.Exit) + } else { + focusManager.moveFocus(FocusDirection.Down) + } + }, ) TokenField( textFieldState = state.nameTextField, placeholder = stringResource(id = R.string.custom_token_name_input_placeholder), title = R.string.custom_token_name_input_title, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next, + ), + onImeAction = { focusManager.moveFocus(FocusDirection.Down) }, ) TokenField( textFieldState = state.symbolTextField, placeholder = stringResource(id = R.string.custom_token_token_symbol_input_placeholder), title = R.string.custom_token_token_symbol_input_title, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next, + ), + onImeAction = { focusManager.moveFocus(FocusDirection.Down) }, ) TokenField( textFieldState = state.decimalsTextField, placeholder = "0", title = R.string.custom_token_decimals_input_title, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + onImeAction = { focusManager.moveFocus(FocusDirection.Exit) }, ) } } @@ -183,7 +228,8 @@ private fun TokenField( textFieldState: TextFieldState, placeholder: String, title: Int, - keyboardType: KeyboardType = KeyboardType.Text, + onImeAction: () -> Unit, + keyboardOptions: KeyboardOptions, ) { Column( modifier = Modifier @@ -198,7 +244,8 @@ private fun TokenField( is TextFieldState.Editable -> TokenTextField( state = textFieldState, placeholder = placeholder, - keyboardType = keyboardType, + onImeAction = onImeAction, + keyboardOptions = keyboardOptions, ) is TextFieldState.Loading -> TokenShimmer() } @@ -243,7 +290,7 @@ private fun TokenTextFieldTitle(state: TextFieldState?, title: String) { @Composable private fun Preview_ChooseDerivationScreen_Light() { TangemTheme(isDark = false) { - CustomTokensScreen(state = AddCustomTokenPreviewData.state) + AddCustomTokenScreen(state = AddCustomTokenPreviewData.state) } } @@ -251,6 +298,6 @@ private fun Preview_ChooseDerivationScreen_Light() { @Composable private fun Preview_ChooseDerivationScreen_Dark() { TangemTheme(isDark = true) { - CustomTokensScreen(state = AddCustomTokenPreviewData.state) + AddCustomTokenScreen(state = AddCustomTokenPreviewData.state) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt similarity index 94% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt index 625ff06edd..3422e1cf8e 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseDerivationScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -16,8 +16,8 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock -import com.tangem.managetokens.presentation.customtokens.state.ChooseDerivationState -import com.tangem.managetokens.presentation.customtokens.state.previewdata.ChooseDerivationPreviewData +import com.tangem.managetokens.presentation.addcustomtoken.state.ChooseDerivationState +import com.tangem.managetokens.presentation.addcustomtoken.state.previewdata.ChooseDerivationPreviewData @Composable internal fun ChooseDerivationScreen(state: ChooseDerivationState, modifier: Modifier = Modifier) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt similarity index 93% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt index c18fac8a6b..4ee903652e 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/ChooseNetworkCustomScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -16,8 +16,8 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.ui.components.NetworkItem -import com.tangem.managetokens.presentation.customtokens.state.ChooseNetworkState -import com.tangem.managetokens.presentation.customtokens.state.previewdata.ChooseNetworkCustomPreviewData +import com.tangem.managetokens.presentation.addcustomtoken.state.ChooseNetworkState +import com.tangem.managetokens.presentation.addcustomtoken.state.previewdata.ChooseNetworkCustomPreviewData @Composable internal fun ChooseNetworkCustomScreen(state: ChooseNetworkState, modifier: Modifier = Modifier) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomDerivationDialog.kt similarity index 90% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomDerivationDialog.kt index db9a76365f..969d7f61fb 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomDerivationDialog.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomDerivationDialog.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource @@ -6,7 +6,7 @@ import com.tangem.core.ui.components.AdditionalTextInputDialogParams import com.tangem.core.ui.components.DialogButton import com.tangem.core.ui.components.TextInputDialog import com.tangem.features.managetokens.impl.R -import com.tangem.managetokens.presentation.customtokens.state.EnterCustomDerivationState +import com.tangem.managetokens.presentation.addcustomtoken.state.EnterCustomDerivationState @Composable internal fun CustomDerivationDialog(state: EnterCustomDerivationState) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomTokenChooseWalletScreen.kt similarity index 91% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomTokenChooseWalletScreen.kt index 358d2d6841..3b8eb7c2af 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/CustomTokensChooseWalletScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/CustomTokenChooseWalletScreen.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/TokenTextField.kt similarity index 59% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/TokenTextField.kt index a615d22738..63521c6775 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/ui/TokenTextField.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/TokenTextField.kt @@ -1,4 +1,4 @@ -package com.tangem.managetokens.presentation.customtokens.ui +package com.tangem.managetokens.presentation.addcustomtoken.ui import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -6,24 +6,38 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.input.key.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.core.ui.res.TangemTheme -import com.tangem.managetokens.presentation.customtokens.state.TextFieldState +import com.tangem.managetokens.presentation.addcustomtoken.state.TextFieldState @Composable internal fun TokenTextField( state: TextFieldState.Editable, placeholder: String, - keyboardType: KeyboardType = KeyboardType.Text, + onImeAction: () -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Default, + ), ) { + val isInitiallyComposed = remember { mutableStateOf(false) } + LaunchedEffect(key1 = true) { + isInitiallyComposed.value = true + } + BasicTextField( value = state.value, onValueChange = state.onValueChange, - keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = ImeAction.Default), + keyboardOptions = keyboardOptions, singleLine = true, maxLines = 1, textStyle = TangemTheme.typography.subtitle1.copy( @@ -32,7 +46,20 @@ internal fun TokenTextField( ), cursorBrush = SolidColor(TangemTheme.colors.icon.primary1), modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .onKeyEvent { keyEvent -> + if (keyEvent.type == KeyEventType.KeyUp && keyEvent.key == Key.Enter) { + onImeAction() + true + } else { + false + } + } + .onFocusChanged { + if (!it.isFocused && isInitiallyComposed.value) { + state.onFocusExit() + } + }, decorationBox = { innerTextField -> Row(modifier = Modifier.fillMaxWidth()) { if (state.value.isEmpty()) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensClickIntents.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenClickIntents.kt similarity index 70% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensClickIntents.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenClickIntents.kt index 6601f3e270..5a2e8e299b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensClickIntents.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenClickIntents.kt @@ -1,9 +1,10 @@ -package com.tangem.managetokens.presentation.customtokens.viewmodels +package com.tangem.managetokens.presentation.addcustomtoken.viewmodels import com.tangem.managetokens.presentation.common.state.NetworkItemState -import com.tangem.managetokens.presentation.customtokens.state.Derivation +import com.tangem.managetokens.presentation.addcustomtoken.state.Derivation -internal interface CustomTokensClickIntents { +@Suppress("TooManyFunctions") +internal interface AddCustomTokenClickIntents { fun onNetworkSelected(networkItemState: NetworkItemState) @@ -25,6 +26,14 @@ internal interface CustomTokensClickIntents { fun onDecimalsChange(input: String) + fun onContractAddressFocusExit() + + fun onTokenNameFocusExit() + + fun onSymbolFocusExit() + + fun onDecimalsFocusExit() + fun onDerivationSelected(derivation: Derivation) fun onChooseDerivationClick() diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensViewModel.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt similarity index 64% rename from features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensViewModel.kt rename to features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt index cc265a1bc2..1efdf2911b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/customtokens/viewmodels/CustomTokensViewModel.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt @@ -1,47 +1,52 @@ -package com.tangem.managetokens.presentation.customtokens.viewmodels +package com.tangem.managetokens.presentation.addcustomtoken.viewmodels +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.HDWalletError import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.tokens.* +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase +import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRouter +import com.tangem.managetokens.presentation.common.analytics.ManageTokens import com.tangem.managetokens.presentation.common.state.AlertState import com.tangem.managetokens.presentation.common.state.Event import com.tangem.managetokens.presentation.common.state.NetworkItemState -import com.tangem.managetokens.presentation.customtokens.state.* -import com.tangem.managetokens.presentation.customtokens.state.factory.AddCustomTokenStateToCryptoCurrencyConverter -import com.tangem.managetokens.presentation.customtokens.state.factory.ContractAddressToCustomTokenDataConverter -import com.tangem.managetokens.presentation.customtokens.state.factory.CustomTokensStateFactory -import com.tangem.managetokens.presentation.customtokens.state.factory.FoundTokenToCustomTokenDataConverter -import com.tangem.managetokens.presentation.router.InnerManageTokensRouter +import com.tangem.managetokens.presentation.addcustomtoken.state.* +import com.tangem.managetokens.presentation.addcustomtoken.state.factory.AddCustomTokenStateToCryptoCurrencyConverter +import com.tangem.managetokens.presentation.addcustomtoken.state.factory.ContractAddressToCustomTokenDataConverter +import com.tangem.managetokens.presentation.addcustomtoken.state.factory.AddCustomTokenStateFactory +import com.tangem.managetokens.presentation.addcustomtoken.state.factory.FoundTokenToCustomTokenDataConverter import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer import com.tangem.utils.coroutines.Debouncer.Companion.DEFAULT_WAIT_TIME_MS import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toPersistentSet +import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch -import kotlinx.coroutines.plus -import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.properties.Delegates -@Suppress("LongParameterList") +@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") +@Stable @HiltViewModel -internal class CustomTokensViewModel @Inject constructor( +internal class AddCustomTokenViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val getWalletsUseCase: GetWalletsUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, @@ -52,21 +57,25 @@ internal class CustomTokensViewModel @Inject constructor( private val validateContractAddressUseCase: ValidateContractAddressUseCase, private val getNetworksSupportedByWallet: GetNetworksSupportedByWallet, private val areTokensSupportedByNetworkUseCase: AreTokensSupportedByNetworkUseCase, -) : ViewModel(), CustomTokensClickIntents, DefaultLifecycleObserver { + private val analyticsEventHandler: AnalyticsEventHandler, +) : ViewModel(), AddCustomTokenClickIntents, DefaultLifecycleObserver { private val debouncer = Debouncer() - private val stateFactory = CustomTokensStateFactory( + private val stateFactory = AddCustomTokenStateFactory( currentStateProvider = Provider { uiState }, clickIntents = this, ) - var router: InnerManageTokensRouter by Delegates.notNull() + var router: AddCustomTokenRouter by Delegates.notNull() var uiState: AddCustomTokenState by mutableStateOf(stateFactory.getInitialState()) private set - init { + /** + * Called when the bottom sheet was opened + */ + fun onInitialize() { viewModelScope.launch(dispatchers.io) { getWalletsUseCase() .distinctUntilChanged() @@ -86,6 +95,20 @@ internal class CustomTokensViewModel @Inject constructor( } } + /** + * Called after the bottom sheet is closed + */ + fun onDispose() { + // We have to manually cancel viewModelScope's child jobs when bottom sheet is closed + viewModelScope.coroutineContext.cancelChildren() + // and reset state + uiState = stateFactory.getInitialState() + } + + override fun onDestroy(owner: LifecycleOwner) { + uiState = stateFactory.getInitialState() + } + private suspend fun selectSuitableWallet(suitableUserWallets: List): UserWalletId? { val selectedWallet = getSelectedWalletSyncUseCase().getOrNull() val selectedWalletId = if (walletSupportsAddingTokens(selectedWallet) && suitableUserWallets.isNotEmpty()) { @@ -110,6 +133,7 @@ internal class CustomTokensViewModel @Inject constructor( } override fun onNetworkSelected(networkItemState: NetworkItemState) { + analyticsEventHandler.send(ManageTokens.CustomTokenNetworkSelected(networkItemState.name)) selectNetwork(networkItemState) router.popBackStack() } @@ -128,7 +152,7 @@ internal class CustomTokensViewModel @Inject constructor( } override fun onChooseNetworkClick() { - router.openCustomTokensChooseNetwork() + router.openCustomTokenChooseNetwork() } override fun onCloseChoosingNetworkClick() { @@ -136,6 +160,7 @@ internal class CustomTokensViewModel @Inject constructor( } override fun onWalletSelected(walletId: String) { + analyticsEventHandler.send(ManageTokens.WalletSelected(ManageTokens.WalletSelected.Source.CustomToken)) viewModelScope.launch(dispatchers.io) { val userWalletId = UserWalletId(walletId) selectWalletUseCase(userWalletId) @@ -152,7 +177,7 @@ internal class CustomTokensViewModel @Inject constructor( } override fun onChooseWalletClick() { - router.openCustomTokensChooseWallet() + router.openCustomTokenChooseWallet() } override fun onCloseChoosingWalletClick() { @@ -166,6 +191,7 @@ internal class CustomTokensViewModel @Inject constructor( value = input, isEnabled = true, onValueChange = this::onContractAddressChange, + onFocusExit = this::onContractAddressFocusExit, ), ), ) @@ -200,22 +226,34 @@ internal class CustomTokensViewModel @Inject constructor( networkId = networkId, ).fold( ifLeft = { - val tokenData = ContractAddressToCustomTokenDataConverter(this@CustomTokensViewModel) + val tokenData = ContractAddressToCustomTokenDataConverter(this@AddCustomTokenViewModel) .convert(contractAddress) + + val isButtonEnabled = tokenData.isRequiredInformationProvided() uiState = uiState.copy( tokenData = tokenData, warnings = (uiState.warnings + AddCustomTokenWarning.PotentialScamToken).toPersistentSet(), + addTokenButton = uiState.addTokenButton.copy( + isEnabled = isButtonEnabled, + ), ) }, ifRight = { token -> val tokenData = if (token != null) { - FoundTokenToCustomTokenDataConverter(this@CustomTokensViewModel).convert(token) + FoundTokenToCustomTokenDataConverter(this@AddCustomTokenViewModel).convert(token) } else { - ContractAddressToCustomTokenDataConverter(this@CustomTokensViewModel).convert( + ContractAddressToCustomTokenDataConverter(this@AddCustomTokenViewModel).convert( contractAddress, ) } - uiState = uiState.copy(tokenData = tokenData) + + val isButtonEnabled = tokenData.isRequiredInformationProvided() + uiState = uiState.copy( + tokenData = tokenData, + addTokenButton = uiState.addTokenButton.copy( + isEnabled = isButtonEnabled, + ), + ) }, ) } @@ -229,8 +267,12 @@ internal class CustomTokensViewModel @Inject constructor( value = input, isEnabled = true, onValueChange = this::onTokenNameChange, + onFocusExit = this::onTokenNameFocusExit, ), ), + addTokenButton = uiState.addTokenButton.copy( + isEnabled = uiState.tokenData?.isRequiredInformationProvided() == true, + ), ) } @@ -241,8 +283,12 @@ internal class CustomTokensViewModel @Inject constructor( value = input, isEnabled = true, onValueChange = this::onSymbolChange, + onFocusExit = this::onSymbolFocusExit, ), ), + addTokenButton = uiState.addTokenButton.copy( + isEnabled = uiState.tokenData?.isRequiredInformationProvided() == true, + ), ) } @@ -260,19 +306,45 @@ internal class CustomTokensViewModel @Inject constructor( isEnabled = true, onValueChange = this::onDecimalsChange, error = error, + onFocusExit = this::onDecimalsFocusExit, ), ), + addTokenButton = uiState.addTokenButton.copy( + isEnabled = uiState.tokenData?.isRequiredInformationProvided() == true, + ), ) } + override fun onContractAddressFocusExit() { + val error = (uiState.tokenData?.contractAddressTextField as? TextFieldState.Editable)?.error + val validated = error !is AddCustomTokenWarning.InvalidContractAddress + analyticsEventHandler.send(ManageTokens.CustomTokenAddress(validated = validated)) + } + + override fun onTokenNameFocusExit() { + analyticsEventHandler.send(ManageTokens.CustomTokenName) + } + + override fun onSymbolFocusExit() { + analyticsEventHandler.send(ManageTokens.CustomTokenSymbol) + } + + override fun onDecimalsFocusExit() { + analyticsEventHandler.send(ManageTokens.CustomTokenDecimals) + } + override fun onDerivationSelected(derivation: Derivation) { - uiState = - uiState.copy(chooseDerivationState = uiState.chooseDerivationState?.copy(selectedDerivation = derivation)) + derivation.standardType?.let { + analyticsEventHandler.send(ManageTokens.CustomTokenDerivationSelected(derivation.networkName)) + } + uiState = uiState.copy( + chooseDerivationState = uiState.chooseDerivationState?.copy(selectedDerivation = derivation), + ) router.popBackStack() } override fun onChooseDerivationClick() { - router.openCustomTokensChooseDerivation() + router.openCustomTokenChooseDerivation() } override fun onCloseChoosingDerivationClick() { @@ -280,6 +352,7 @@ internal class CustomTokensViewModel @Inject constructor( } override fun onCustomDerivationChange(input: String) { + analyticsEventHandler.send(ManageTokens.CustomTokenDerivationSelected(ManageTokens.Derivation.CUSTOM.value)) uiState = uiState.copy( chooseDerivationState = uiState.chooseDerivationState?.copy( enterCustomDerivationState = uiState.chooseDerivationState?.enterCustomDerivationState?.copy( @@ -332,21 +405,77 @@ internal class CustomTokensViewModel @Inject constructor( val cryptoCurrency = AddCustomTokenStateToCryptoCurrencyConverter( selectedWallet.scanResponse.derivationStyleProvider, ).convert(uiState) - val alreadyAdded = - getCurrenciesUseCase(selectedWallet.walletId).getOrNull()?.any { it == cryptoCurrency } - if (alreadyAdded == true) { + val alreadyAdded = isCryptoCurrencyAlreadyAdded(selectedWallet, cryptoCurrency) + if (alreadyAdded) { uiState = stateFactory.getStateAndTriggerEvent( state = uiState, event = Event.ShowAlert(AlertState.TokenAlreadyAdded), setUiState = { uiState = it }, ) } else { + sendTokenAddedEvent(cryptoCurrency) addCryptoCurrenciesUseCase(selectedWallet.walletId, currency = cryptoCurrency) withContext(dispatchers.main) { router.popBackStack() } } } } + private suspend fun isCryptoCurrencyAlreadyAdded( + selectedWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ): Boolean { + val currenciesList = getCurrenciesUseCase(selectedWallet.walletId).getOrElse { emptyList() } + return when (cryptoCurrency) { + is CryptoCurrency.Coin -> { + currenciesList.any { + it is CryptoCurrency.Coin && + it.id == cryptoCurrency.id && + it.network.derivationPath == cryptoCurrency.network.derivationPath + } + } + is CryptoCurrency.Token -> { + currenciesList.any { + (it as? CryptoCurrency.Token)?.let { + it.id == cryptoCurrency.id && + it.contractAddress == cryptoCurrency.contractAddress && + it.network.id == cryptoCurrency.network.id && + it.network.derivationPath == cryptoCurrency.network.derivationPath + } ?: false + } + } + } + } + + private fun sendTokenAddedEvent(cryptoCurrency: CryptoCurrency) { + val selectedDerivation = uiState.chooseDerivationState?.selectedDerivation + + val derivation = when { + selectedDerivation == null -> ManageTokens.Derivation.DEFAULT.value + selectedDerivation.networkName.isNotEmpty() -> selectedDerivation.networkName + else -> ManageTokens.Derivation.CUSTOM.value + } + when (cryptoCurrency) { + is CryptoCurrency.Token -> { + analyticsEventHandler.send( + ManageTokens.CustomTokenWasAdded( + derivation = derivation, + networkId = cryptoCurrency.network.name, + contractAddress = cryptoCurrency.contractAddress, + token = cryptoCurrency.symbol, + ), + ) + } + is CryptoCurrency.Coin -> { + analyticsEventHandler.send( + ManageTokens.CustomTokenWasAdded( + derivation = derivation, + networkId = cryptoCurrency.network.name, + ), + ) + } + } + } + override fun onBack() { router.popBackStack() } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/analytics/ManageTokens.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/analytics/ManageTokens.kt new file mode 100644 index 0000000000..3a527de7f2 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/analytics/ManageTokens.kt @@ -0,0 +1,102 @@ +package com.tangem.managetokens.presentation.common.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam + +sealed class ManageTokens( + event: String, + params: Map = emptyMap(), +) : AnalyticsEvent("Manage Tokens", event, params) { + + class ScreenOpened : ManageTokens("Manage Tokens Screen Opened") + + class TokenIsNotFound(userInput: String) : ManageTokens( + event = "Token Is Not Found", + params = mapOf("Input" to userInput), + ) + + class TokenSwitcherChanged( + token: String, + state: AnalyticsParam.OnOffState, + ) : ManageTokens( + event = "Token Switcher Changed", + params = mapOf( + "Token" to token, + "State" to state.value, + ), + ) + + class ButtonAdd(token: String) : ManageTokens( + event = "Button - Add", + params = mapOf("Token" to token), + ) + + class ButtonEdit(token: String) : ManageTokens( + event = "Button - Edit", + params = mapOf("Token" to token), + ) + + object ButtonChooseWallet : ManageTokens(event = "Button - Choose Wallet") + + class WalletSelected(source: Source) : ManageTokens( + event = "Wallet Selected", + params = mapOf("Source" to source.name), + ) { + + enum class Source(name: String) { + MainToken("Main Token"), + CustomToken("Custom Token"), + } + } + + object NoticeNonNativeNetworkClicked : ManageTokens(event = "Notice - Non Native Network Clicked") + + class ButtonGenerateAddresses(cardCount: Int) : ManageTokens( + event = "Button - Get Addresses", + params = mapOf("CardCount" to cardCount.toString()), + ) + + object ButtonCustomToken : ManageTokens("Button - Custom Token") + + class CustomTokenWasAdded( + val derivation: String, + val networkId: String, + val token: String? = null, + val contractAddress: String? = null, + ) : ManageTokens( + event = "Custom Token Was Added", + params = mutableMapOf( + "Derivation" to derivation, + "Network Id" to networkId, + ).apply { + token?.let { put("Token", it) } + contractAddress?.let { put("Contract Address", it) } + }, + ) + + class CustomTokenNetworkSelected(blockchain: String) : ManageTokens( + event = "Custom Token Network Selected", + params = mapOf("blockchain" to blockchain), + ) + + class CustomTokenDerivationSelected(derivation: String) : ManageTokens( + event = "Custom Token Derivation Selected", + params = mapOf("Derivation" to derivation), + ) + + class CustomTokenAddress(validated: Boolean) : ManageTokens( + "Custom Token Address", + params = mapOf("Validation" to if (validated) "Ok" else "Error"), + ) + + object CustomTokenName : ManageTokens("Custom Token Name") + + object CustomTokenSymbol : ManageTokens("Custom Token Symbol") + + object CustomTokenDecimals : ManageTokens("Custom Token Decimals") + + enum class Derivation(val value: String) { + DEFAULT("Default"), + CUSTOM("Custom"), + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt index 7f57f95a84..6255c750f9 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt @@ -38,7 +38,7 @@ internal sealed interface NetworkItemState { * @property onToggleClick lambda be invoked when switch is been toggled */ @Suppress("LongParameterList") - class Toggleable( + data class Toggleable( override val name: String, override val protocolName: String, override val id: String, @@ -72,7 +72,7 @@ internal sealed interface NetworkItemState { * @property onNetworkClick lambda be invoked when network item is been clicked * */ - class Selectable( + data class Selectable( override val name: String, override val protocolName: String, val iconResId: Int, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt index 1ee0ecebce..6738d56d3f 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt @@ -1,6 +1,7 @@ package com.tangem.managetokens.presentation.managetokens.state import androidx.paging.PagingData +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.event.StateEvent import com.tangem.managetokens.presentation.common.state.ChooseWalletState import com.tangem.managetokens.presentation.common.state.Event @@ -15,7 +16,9 @@ internal data class ManageTokensState( val derivationNotification: DerivationNotificationState? = null, val selectedToken: TokenItemState.Loaded? = null, val showChooseWalletScreen: Boolean = false, + val customTokenBottomSheetConfig: TangemBottomSheetConfig, val event: StateEvent, + val onEmptySearchResult: (String) -> Unit, ) data class AddCustomTokenButton( diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt index bf81e5bc4c..d7531b2e85 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt @@ -1,6 +1,8 @@ package com.tangem.managetokens.presentation.managetokens.state.factory import androidx.paging.PagingData +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.domain.tokens.CurrencyCompatibilityError @@ -10,6 +12,7 @@ import com.tangem.managetokens.presentation.common.state.* import com.tangem.managetokens.presentation.common.utils.CurrencyUtils import com.tangem.managetokens.presentation.managetokens.state.* import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensClickIntents +import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensUiEvents import com.tangem.utils.Provider import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.Flow @@ -17,6 +20,7 @@ import kotlinx.coroutines.flow.Flow internal class ManageTokensStateFactory( private val currentStateProvider: Provider, private val clickIntents: ManageTokensClickIntents, + private val uiIntents: ManageTokensUiEvents, ) { fun getInitialState(tokens: Flow>): ManageTokensState { @@ -36,6 +40,12 @@ internal class ManageTokensStateFactory( isLoading = false, event = consumedEvent(), chooseWalletState = ChooseWalletState.NoSelection, + onEmptySearchResult = uiIntents::onEmptySearchResult, + customTokenBottomSheetConfig = TangemBottomSheetConfig( + isShow = false, + onDismissRequest = uiIntents::onAddCustomTokenSheetDismissed, + content = TangemBottomSheetConfigContent.Empty, + ), ) } @@ -176,7 +186,7 @@ internal class ManageTokensStateFactory( totalNeeded = totalNeeded, totalWallets = totalWallets, walletsToDerive = walletsToDerive, - onGenerateClick = clickIntents::onGenerateDerivationClick, + onGenerateClick = clickIntents::onGetAddressesClick, ) } return currentStateProvider().copy(derivationNotification = derivationNotificationState) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt index e9fedc1587..f66b65bc1b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt @@ -37,7 +37,7 @@ internal val nonNativeNetworks = listOf( iconResId = mutableStateOf(R.drawable.img_kusama_22), isMainNetwork = false, isAdded = mutableStateOf(true), - id = "", + id = "1", onToggleClick = { _, _ -> }, address = "", decimals = 0, @@ -48,7 +48,7 @@ internal val nonNativeNetworks = listOf( iconResId = mutableStateOf(R.drawable.ic_bsc_16), isMainNetwork = false, isAdded = mutableStateOf(false), - id = "", + id = "2", onToggleClick = { _, _ -> }, address = "", decimals = 0, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt index 6ea8316ba5..40d4334eb3 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt @@ -1,6 +1,8 @@ package com.tangem.managetokens.presentation.managetokens.state.previewdata import androidx.paging.PagingData +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.event.consumedEvent import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton @@ -19,6 +21,8 @@ internal object ManageTokensStatePreviewData { derivationNotification = DerivationNotificationStatePreviewData.state, event = consumedEvent(), chooseWalletState = ChooseWalletStatePreviewData.state, + onEmptySearchResult = {}, + customTokenBottomSheetConfig = TangemBottomSheetConfig(false, {}, TangemBottomSheetConfigContent.Empty), ) val loadingState: ManageTokensState diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt index 444df9d0fe..ead0700da2 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt @@ -84,29 +84,18 @@ internal fun ChooseNetworkScreen( SpacerH(height = TangemTheme.dimens.spacing16) } - item { - if (networkState.nativeNetworks.isNotEmpty()) { - NativeNetworks(networkState = networkState, tokenState = state) - } + if (networkState.nativeNetworks.isNotEmpty()) { + this@LazyColumn.nativeNetworks(networkState = networkState, tokenState = state) } if (networkState.nonNativeNetworks.isNotEmpty()) { - item { - NonNativeNetworksHeader(networkState.onNonNativeNetworkHintClick) - } - item { - SpacerH(height = TangemTheme.dimens.spacing8) - } - item { - this@LazyColumn.NonNativeNetworks(networkState = networkState, tokenState = state) - } + this@LazyColumn.nonNativeNetworks(networkState = networkState, tokenState = state) } } } -@Composable -private fun NativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { - Column { +private fun LazyListScope.nativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { + item { Text( text = stringResource(id = R.string.manage_tokens_network_selector_native_title), color = TangemTheme.colors.text.tertiary, @@ -119,25 +108,35 @@ private fun NativeNetworks(networkState: ChooseNetworkState, tokenState: TokenIt style = TangemTheme.typography.caption2, ) SpacerH(height = TangemTheme.dimens.spacing8) + } - networkState.nativeNetworks.forEachIndexed { index, network -> - NetworkItem( - state = network, - tokenState = tokenState, - modifier = Modifier - .roundedShapeItemDecoration( - currentIndex = index, - lastIndex = networkState.nativeNetworks.lastIndex, - addDefaultPadding = false, - ), - ) - } + items( + count = networkState.nativeNetworks.count(), + key = { index -> networkState.nativeNetworks[index].id }, + ) { index -> + NetworkItem( + state = networkState.nativeNetworks[index], + tokenState = tokenState, + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = networkState.nativeNetworks.lastIndex, + addDefaultPadding = false, + ), + ) + } + + item { SpacerH(height = TangemTheme.dimens.spacing16) } } -@Composable -private fun LazyListScope.NonNativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { +private fun LazyListScope.nonNativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { + item { + NonNativeNetworksHeader(networkState.onNonNativeNetworkHintClick) + SpacerH(height = TangemTheme.dimens.spacing8) + } + items( count = networkState.nonNativeNetworks.count(), key = { index -> networkState.nonNativeNetworks[index].id }, @@ -153,6 +152,7 @@ private fun LazyListScope.NonNativeNetworks(networkState: ChooseNetworkState, to ), ) } + item { SpacerH(height = TangemTheme.dimens.spacing16) } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt index 1bbf0c61a9..12f7a0a191 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt @@ -2,21 +2,28 @@ package com.tangem.managetokens.presentation.managetokens.ui import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Surface import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems +import com.tangem.core.ui.components.Keyboard +import com.tangem.core.ui.components.SpacerH18 import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenBottomSheet import com.tangem.managetokens.presentation.common.state.AlertState import com.tangem.managetokens.presentation.common.state.ChooseWalletState import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheet @@ -31,7 +38,7 @@ import com.tangem.managetokens.presentation.managetokens.ui.components.TokensLis import com.tangem.managetokens.presentation.managetokens.ui.components.TokensSearchBar @Composable -internal fun ManageTokensScreen(state: ManageTokensState) { +internal fun ManageTokensScreen(state: ManageTokensState, onHeaderSizeChange: (Dp) -> Unit) { var alertState by remember { mutableStateOf(value = null) } EventEffect( @@ -42,53 +49,108 @@ internal fun ManageTokensScreen(state: ManageTokensState) { Alert(state = it, onDismiss = { alertState = null }) } - Content(state) + Content(state = state, onHeaderSizeChange = onHeaderSizeChange) + + AddCustomTokenBottomSheet(state.customTokenBottomSheetConfig) } @Composable -private fun Content(state: ManageTokensState) { +private fun Content(state: ManageTokensState, onHeaderSizeChange: (Dp) -> Unit) { + val keyboard by keyboardAsState() + val density = LocalDensity.current + var tokenListAlertBottomPadding by remember(keyboard is Keyboard.Opened) { mutableStateOf(0.dp) } + Box( modifier = Modifier .fillMaxSize() - .background(color = TangemTheme.colors.background.primary) - .padding(top = TangemTheme.dimens.spacing32), + .navigationBarsPadding() + .imePadding() + .background(color = TangemTheme.colors.background.primary), ) { Column { val listState = rememberLazyListState() val raiseSearchBar by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } - val elevation by animateDpAsState( - targetValue = if (raiseSearchBar) { - TangemTheme.dimens.elevation8 - } else { - TangemTheme.dimens.elevation0 - }, + targetValue = if (raiseSearchBar) TangemTheme.dimens.elevation8 else TangemTheme.dimens.elevation0, label = "top_bar_elevation", ) + Surface( elevation = elevation, - modifier = Modifier, + modifier = Modifier.onGloballyPositioned { + with(density) { onHeaderSizeChange(it.size.height.toDp()) } + }, ) { TokensSearchBar( state = state.searchBarState, modifier = Modifier .background(color = TangemTheme.colors.background.primary) - .padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing20), + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing4, + ), ) } + + SpacerH18() + val tokens = state.tokens.collectAsLazyPagingItems() - TokensList(tokens = tokens, addCustomTokenButton = state.addCustomTokenButton) - } - state.derivationNotification?.let { - DerivationNotification( - config = it.config, - modifier = Modifier - .align(Alignment.BottomCenter), + val query = state.searchBarState.query + + TrackPossibleEmptySearchResult( + tokens = tokens, + query = query, + onEmptySearchResult = state.onEmptySearchResult, + ) + + TokensList( + modifier = Modifier.padding(bottom = tokenListAlertBottomPadding), + tokens = tokens, + addCustomTokenButton = state.addCustomTokenButton, ) } + state.selectedToken?.let { selectedToken -> ManageTokensBottomSheet(selectedToken = selectedToken, state = state) } + + state.derivationNotification?.let { + if (keyboard is Keyboard.Closed) { + DerivationNotification( + config = it.config, + modifier = Modifier + .align(Alignment.BottomCenter) + .onGloballyPositioned { + with(density) { tokenListAlertBottomPadding = it.size.height.toDp() } + }, + ) + DisposableEffect(Unit) { + onDispose { tokenListAlertBottomPadding = 0.dp } + } + } + } + } +} + +@Composable +private fun TrackPossibleEmptySearchResult( + tokens: LazyPagingItems, + query: String, + onEmptySearchResult: (String) -> Unit, +) { + val wasLoading = remember { mutableStateOf(false) } + + LaunchedEffect(tokens.loadState) { + val isLoading = tokens.loadState.refresh == LoadState.Loading + val stoppedLoading = wasLoading.value && !isLoading + val queryAndTokensCondition = query.isNotEmpty() && tokens.itemSnapshotList.isEmpty() + + if (stoppedLoading && queryAndTokensCondition) { + onEmptySearchResult(query) + } + + wasLoading.value = isLoading } } @@ -121,7 +183,7 @@ private fun Preview_ManageTokensScreen_LightTheme( state: ManageTokensState, ) { TangemTheme(isDark = false) { - ManageTokensScreen(state) + ManageTokensScreen(state) {} } } @@ -132,7 +194,7 @@ private fun Preview_ManageTokensScreen_DarkTheme( state: ManageTokensState, ) { TangemTheme(isDark = true) { - ManageTokensScreen(state) + ManageTokensScreen(state) {} } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt index 475f9a8687..d6c2b505ea 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt @@ -17,8 +17,12 @@ import com.tangem.managetokens.presentation.managetokens.state.TokenItemState private const val PLACEHOLDER_ITEMS_COUNT = 50 @Composable -internal fun TokensList(tokens: LazyPagingItems, addCustomTokenButton: AddCustomTokenButton) { - LazyColumn { +internal fun TokensList( + tokens: LazyPagingItems, + addCustomTokenButton: AddCustomTokenButton, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier) { item { Text( text = stringResource(id = R.string.manage_tokens_title), diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensClickIntents.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensClickIntents.kt index a86b2cd1bb..2b6ba5b3bd 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensClickIntents.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensClickIntents.kt @@ -4,23 +4,24 @@ import com.tangem.managetokens.presentation.common.state.NetworkItemState import com.tangem.managetokens.presentation.managetokens.state.TokenItemState internal interface ManageTokensClickIntents { + fun onAddCustomTokensButtonClick() fun onSearchQueryChange(query: String) + fun onSearchActiveChange(active: Boolean) fun onTokenItemButtonClick(token: TokenItemState.Loaded) - fun onGenerateDerivationClick() + fun onGetAddressesClick() fun onBackClick() fun onCloseChooseNetworkScreen() fun onNetworkToggleClick(token: TokenItemState.Loaded, network: NetworkItemState.Toggleable) - fun onNonNativeNetworkHintClick() - fun onSelectWalletsClick() + fun onNonNativeNetworkHintClick() fun onChooseWalletClick() diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt new file mode 100644 index 0000000000..663871cf2a --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt @@ -0,0 +1,8 @@ +package com.tangem.managetokens.presentation.managetokens.viewmodels + +internal interface ManageTokensUiEvents { + + fun onEmptySearchResult(query: String) + + fun onAddCustomTokenSheetDismissed() +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt index ae04c6d7cd..ad42c3159e 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt @@ -3,12 +3,13 @@ package com.tangem.managetokens.presentation.managetokens.viewmodels import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.map import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.card.DerivePublicKeysUseCase @@ -20,6 +21,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase +import com.tangem.managetokens.presentation.common.analytics.ManageTokens import com.tangem.managetokens.presentation.common.state.AlertState import com.tangem.managetokens.presentation.common.state.Event import com.tangem.managetokens.presentation.common.state.NetworkItemState @@ -27,7 +29,6 @@ import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState import com.tangem.managetokens.presentation.managetokens.state.TokenButtonType import com.tangem.managetokens.presentation.managetokens.state.TokenItemState import com.tangem.managetokens.presentation.managetokens.state.factory.* -import com.tangem.managetokens.presentation.router.InnerManageTokensRouter import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer @@ -58,17 +59,17 @@ internal class ManageTokensViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val checkCurrencyCompatibilityUseCase: CheckCurrencyCompatibilityUseCase, private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, -) : ViewModel(), ManageTokensClickIntents, DefaultLifecycleObserver { + private val analyticsEventHandler: AnalyticsEventHandler, +) : ViewModel(), ManageTokensClickIntents, ManageTokensUiEvents { private val debouncer = Debouncer() private val stateFactory = ManageTokensStateFactory( currentStateProvider = Provider { uiState }, clickIntents = this, + uiIntents = this, ) - var router: InnerManageTokensRouter by Delegates.notNull() - var uiState: ManageTokensState by mutableStateOf(stateFactory.getInitialState(flowOf(PagingData.from(emptyList())))) private set @@ -80,7 +81,7 @@ internal class ManageTokensViewModel @Inject constructor( private var selectedWallet: UserWallet? = null - private var neededDerivations: Map> = emptyMap() + private var currenciesToGenerateAddresses: Map> = emptyMap() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() @@ -107,6 +108,8 @@ internal class ManageTokensViewModel @Inject constructor( ) init { + analyticsEventHandler.send(ManageTokens.ScreenOpened()) + viewModelScope.launch(dispatchers.io) { getWalletsUseCase() .distinctUntilChanged() @@ -151,7 +154,7 @@ internal class ManageTokensViewModel @Inject constructor( .distinctUntilChanged() .collectLatest { it.onRight { mapOfMissingDerivations -> - neededDerivations = mapOfMissingDerivations + currenciesToGenerateAddresses = mapOfMissingDerivations withContext(dispatchers.main) { updateDerivation() } } } @@ -159,8 +162,8 @@ internal class ManageTokensViewModel @Inject constructor( } private fun updateDerivation() { - val totalNeeded = neededDerivations.values.sumOf { derivations -> derivations.size } - val walletsToDerive = neededDerivations.values + val totalNeeded = currenciesToGenerateAddresses.values.sumOf { derivations -> derivations.size } + val walletsToDerive = currenciesToGenerateAddresses.values .filter { derivations -> derivations.isNotEmpty() }.size uiState = stateFactory.updateDerivationNotification( totalNeeded = totalNeeded, @@ -182,7 +185,8 @@ internal class ManageTokensViewModel @Inject constructor( } override fun onAddCustomTokensButtonClick() { - router.openCustomTokensScreen() + analyticsEventHandler.send(ManageTokens.ButtonCustomToken) + uiState = uiState.copy(customTokenBottomSheetConfig = uiState.customTokenBottomSheetConfig.copy(isShow = true)) } override fun onSearchQueryChange(query: String) { @@ -201,6 +205,13 @@ internal class ManageTokensViewModel @Inject constructor( override fun onTokenItemButtonClick(token: TokenItemState.Loaded) { when (token.availableAction.value) { TokenButtonType.ADD, TokenButtonType.EDIT -> { + if (token.availableAction.value == TokenButtonType.ADD) { + analyticsEventHandler.send(ManageTokens.ButtonAdd(token.currencySymbol)) + } + if (token.availableAction.value == TokenButtonType.EDIT) { + analyticsEventHandler.send(ManageTokens.ButtonEdit(token.currencySymbol)) + } + uiState = uiState.copy(selectedToken = token) val addedCurrenciesOnWallet = addedCurrenciesByWallet[selectedWallet] ?: listOf() stateFactory.updateTokenNetworksOnTokenSelection(token, addedCurrenciesOnWallet) @@ -219,17 +230,21 @@ internal class ManageTokensViewModel @Inject constructor( } } - override fun onGenerateDerivationClick() { - if (neededDerivations.isNotEmpty()) { + override fun onGetAddressesClick() { + if (currenciesToGenerateAddresses.isNotEmpty()) { viewModelScope.launch(dispatchers.io) { - val walletId = neededDerivations.keys.firstOrNull() - val currenciesToDerive = neededDerivations[walletId] - if (walletId == null || currenciesToDerive.isNullOrEmpty()) return@launch - derivePublicKeysUseCase(walletId, currenciesToDerive) - .onRight { - updateDerivationNotificationState() - fetchTokenListUseCase(userWalletId = walletId) + val cardCount = currenciesToGenerateAddresses.count { it.value.isNotEmpty() } + analyticsEventHandler.send(ManageTokens.ButtonGenerateAddresses(cardCount)) + + currenciesToGenerateAddresses.forEach { (walletId, currenciesToDerive) -> + if (currenciesToDerive.isNotEmpty()) { + derivePublicKeysUseCase(walletId, currenciesToDerive) + .onRight { + updateDerivationNotificationState() + fetchTokenListUseCase(userWalletId = walletId) + } } + } } } } @@ -247,8 +262,14 @@ internal class ManageTokensViewModel @Inject constructor( if (!selectedWallet.isMultiCurrency || selectedWallet.isLocked) return if (network.isAdded.value) { + analyticsEventHandler.send( + ManageTokens.TokenSwitcherChanged(token = token.currencySymbol, AnalyticsParam.OnOffState.Off), + ) toggleToken(token, network, selectedWallet) } else { + analyticsEventHandler.send( + ManageTokens.TokenSwitcherChanged(token = token.currencySymbol, AnalyticsParam.OnOffState.On), + ) viewModelScope.launch(dispatchers.io) { checkCompatibilityAndToggleToken(token, network, selectedWallet) } @@ -298,7 +319,6 @@ internal class ManageTokensViewModel @Inject constructor( "It is only null if Blockchain is Unknown, which mustn't happen here" } if (!network.isAdded.value) { - updateUi(token, network) addedCurrenciesByWallet[selectedWallet]?.add(cryptoCurrency) allAddedCurrencies.add(cryptoCurrency) viewModelScope.launch(dispatchers.io) { @@ -307,13 +327,14 @@ internal class ManageTokensViewModel @Inject constructor( currency = cryptoCurrency, ) } + updateUi(token, network) } else { viewModelScope.launch(dispatchers.io) { if (canBeRemovedAndShowAlertIfNot(selectedWallet.walletId, cryptoCurrency)) { - withContext(dispatchers.main) { updateUi(token, network) } addedCurrenciesByWallet[selectedWallet]?.remove(cryptoCurrency) allAddedCurrencies.remove(cryptoCurrency) removeCurrencyUseCase(selectedWallet.walletId, cryptoCurrency) + withContext(dispatchers.main) { updateUi(token, network) } } } } @@ -348,6 +369,7 @@ internal class ManageTokensViewModel @Inject constructor( } override fun onNonNativeNetworkHintClick() { + analyticsEventHandler.send(ManageTokens.NoticeNonNativeNetworkClicked) uiState = stateFactory.getStateAndTriggerEvent( state = uiState, event = Event.ShowAlert(AlertState.NonNative), @@ -355,13 +377,8 @@ internal class ManageTokensViewModel @Inject constructor( ) } - override fun onSelectWalletsClick() { - uiState = uiState.copy( - showChooseWalletScreen = true, - ) - } - override fun onChooseWalletClick() { + analyticsEventHandler.send(ManageTokens.ButtonChooseWallet) uiState = uiState.copy( showChooseWalletScreen = true, ) @@ -374,6 +391,7 @@ internal class ManageTokensViewModel @Inject constructor( } override fun onWalletSelected(walletId: String) { + analyticsEventHandler.send(ManageTokens.WalletSelected(ManageTokens.WalletSelected.Source.MainToken)) viewModelScope.launch(dispatchers.io) { selectWalletUseCase(UserWalletId(walletId)) } @@ -381,4 +399,12 @@ internal class ManageTokensViewModel @Inject constructor( uiState.selectedToken?.let { onTokenItemButtonClick(it) } uiState = stateFactory.updateSelectedWallet(selectedWalletId = selectedWallet?.walletId?.stringValue) } + + override fun onEmptySearchResult(query: String) { + analyticsEventHandler.send(ManageTokens.TokenIsNotFound(query)) + } + + override fun onAddCustomTokenSheetDismissed() { + uiState = uiState.copy(customTokenBottomSheetConfig = uiState.customTokenBottomSheetConfig.copy(isShow = false)) + } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt deleted file mode 100644 index dca7527d4b..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.tangem.managetokens.presentation.router - -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.fragment.app.Fragment -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.ViewModelStoreOwner -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navigation -import com.tangem.core.navigation.AppScreen -import com.tangem.core.navigation.NavigationAction -import com.tangem.core.navigation.ReduxNavController -import com.tangem.managetokens.ManageTokensFragment -import com.tangem.managetokens.presentation.common.state.ChooseWalletState -import com.tangem.managetokens.presentation.customtokens.ui.ChooseDerivationScreen -import com.tangem.managetokens.presentation.customtokens.ui.ChooseNetworkCustomScreen -import com.tangem.managetokens.presentation.customtokens.ui.CustomTokensChooseWalletScreen -import com.tangem.managetokens.presentation.customtokens.ui.CustomTokensScreen -import com.tangem.managetokens.presentation.customtokens.viewmodels.CustomTokensViewModel -import com.tangem.managetokens.presentation.managetokens.ui.ManageTokensScreen -import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensViewModel -import kotlin.properties.Delegates - -internal class DefaultManageTokensRouter( - private val reduxNavController: ReduxNavController, -) : InnerManageTokensRouter { - - private var navController: NavHostController by Delegates.notNull() - - override fun getEntryFragment(): Fragment = ManageTokensFragment() - - @Composable - override fun Initialize(viewModelStoreOwner: ViewModelStoreOwner) { - NavHost( - navController = rememberNavController().apply { navController = this }, - startDestination = ManageTokensRoute.ManageTokens.route, - ) { - composable(ManageTokensRoute.ManageTokens.route) { - val viewModel = hiltViewModel().apply { router = this@DefaultManageTokensRouter } - LocalLifecycleOwner.current.lifecycle.addObserver(viewModel) - - ManageTokensScreen(state = viewModel.uiState) - } - - navigation( - startDestination = ManageTokensRoute.CustomTokens.Main.route, - route = ManageTokensRoute.CustomTokens.route, - ) { - composable( - ManageTokensRoute.CustomTokens.Main.route, - ) { - val viewModel = hiltViewModel(viewModelStoreOwner).apply { - router = this@DefaultManageTokensRouter - } - CustomTokensScreen(state = viewModel.uiState) - } - composable( - ManageTokensRoute.CustomTokens.ChooseNetwork.route, - ) { - val viewModel = hiltViewModel(viewModelStoreOwner).apply { - router = this@DefaultManageTokensRouter - } - ChooseNetworkCustomScreen( - state = viewModel.uiState.chooseNetworkState, - ) - } - composable( - ManageTokensRoute.CustomTokens.ChooseDerivation.route, - ) { - val viewModel = hiltViewModel(viewModelStoreOwner).apply { - router = this@DefaultManageTokensRouter - } - ChooseDerivationScreen( - state = requireNotNull(viewModel.uiState.chooseDerivationState), - ) - } - composable( - ManageTokensRoute.CustomTokens.ChooseWallet.route, - ) { - val viewModel = hiltViewModel(viewModelStoreOwner).apply { - router = this@DefaultManageTokensRouter - } - CustomTokensChooseWalletScreen( - state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose, - ) - } - } - } - } - - override fun popBackStack(screen: AppScreen?) { - if (screen != null) { - reduxNavController.navigate(action = NavigationAction.PopBackTo(screen)) - } else { - navController.popBackStack() - } - } - - override fun openManageTokensScreen() { - navController.navigate(ManageTokensRoute.ManageTokens.route) - } - - override fun openCustomTokensScreen() { - navController.navigate(ManageTokensRoute.CustomTokens.route) - } - - override fun openCustomTokensChooseNetwork() { - navController.navigate(ManageTokensRoute.CustomTokens.ChooseNetwork.route) - } - - override fun openCustomTokensChooseDerivation() { - navController.navigate(ManageTokensRoute.CustomTokens.ChooseDerivation.route) - } - - override fun openCustomTokensChooseWallet() { - navController.navigate(ManageTokensRoute.CustomTokens.ChooseWallet.route) - } -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt deleted file mode 100644 index 29d0cd4c9e..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.managetokens.presentation.router - -import androidx.compose.runtime.Composable -import androidx.lifecycle.ViewModelStoreOwner -import com.tangem.core.navigation.AppScreen -import com.tangem.features.managetokens.navigation.ManageTokensRouter - -internal interface InnerManageTokensRouter : ManageTokensRouter { - /** - * Initialize router - **/ - @Suppress("TopLevelComposableFunctions") - @Composable - fun Initialize(viewModelStoreOwner: ViewModelStoreOwner) - - /** Pop back stack */ - fun popBackStack(screen: AppScreen? = null) - - /** Open manage tokens screen */ - fun openManageTokensScreen() - - /** Open custom tokens screen */ - fun openCustomTokensScreen() - - fun openCustomTokensChooseNetwork() - - fun openCustomTokensChooseDerivation() - - fun openCustomTokensChooseWallet() -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt deleted file mode 100644 index 0e6a358743..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.managetokens.presentation.router - -/** - * Manage Tokens screens - * - * @property route route string representation - * - */ -internal sealed class ManageTokensRoute(val route: String) { - - object ManageTokens : ManageTokensRoute(route = "manage_tokens") - - object CustomTokens : ManageTokensRoute(route = "manage_tokens/custom_tokens") { - object Main : ManageTokensRoute(CustomTokens.route + "/main") - object ChooseNetwork : ManageTokensRoute(CustomTokens.route + "/choose_network") - object ChooseWallet : ManageTokensRoute(CustomTokens.route + "/choose_wallet") - object ChooseDerivation : ManageTokensRoute(CustomTokens.route + "/choose_derivation") - } -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt new file mode 100644 index 0000000000..3dd460d06f --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt @@ -0,0 +1,22 @@ +package com.tangem.managetokens.presentation.router + +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.tangem.features.managetokens.navigation.ManageTokensUi +import com.tangem.managetokens.presentation.managetokens.ui.ManageTokensScreen +import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensViewModel +import javax.inject.Inject + +internal class ManageTokensUiImpl @Inject constructor() : ManageTokensUi { + + @Composable + override fun Content(onHeaderSizeChange: (Dp) -> Unit) { + val viewModel = hiltViewModel() + + ManageTokensScreen( + state = viewModel.uiState, + onHeaderSizeChange = onHeaderSizeChange, + ) + } +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 379d153a48..154764b162 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -4,30 +4,23 @@ import arrow.core.getOrElse import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.feature.referral.domain.converter.TokensConverter import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData -import com.tangem.features.tester.api.TesterFeatureToggles -import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.UserWalletManager import timber.log.Timber @Suppress("LongParameterList") internal class ReferralInteractorImpl( private val repository: ReferralRepository, - private val derivationManager: DerivationManager, private val userWalletManager: UserWalletManager, - private val tokensConverter: TokensConverter, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, - private val testerFeatureToggles: TesterFeatureToggles, ) : ReferralInteractor { private val tokensForReferral = mutableListOf() - override val isDemoMode: Boolean - get() = repository.isDemoMode + override val isDemoMode: Boolean get() = repository.isDemoMode override suspend fun getReferralStatus(): ReferralData { val referralData = repository.getReferralData(userWalletManager.getWalletId()) @@ -38,19 +31,9 @@ internal class ReferralInteractorImpl( } override suspend fun startReferral(): ReferralData { - return if (tokensForReferral.isNotEmpty()) { - if (testerFeatureToggles.isDerivePublicKeysRefactoringEnabled) { - startReferralNew(tokenData = tokensForReferral.first()) - } else { - // TODO: delete [REDACTED_JIRA] - startReferralLegacy(tokenData = tokensForReferral.first()) - } - } else { - error("Tokens for ref is empty") - } - } + if (tokensForReferral.isEmpty()) error("Tokens for ref is empty") - private suspend fun startReferralNew(tokenData: TokenData): ReferralData { + val tokenData = tokensForReferral.first() val userWallet = getSelectedWalletSyncUseCase().getOrElse { error("Failed to get selected wallet: $it") } @@ -79,18 +62,6 @@ internal class ReferralInteractorImpl( ) } - private suspend fun startReferralLegacy(tokenData: TokenData): ReferralData { - val currency = tokensConverter.convert(tokenData) - val derivationPath = derivationManager.deriveAndAddTokens(currency) - val publicAddress = userWalletManager.getWalletAddress(currency.networkId, derivationPath) - return repository.startReferral( - walletId = userWalletManager.getWalletId(), - networkId = currency.networkId, - tokenId = currency.id, - address = publicAddress, - ) - } - private fun saveReferralTokens(tokens: List) { tokensForReferral.clear() tokensForReferral.addAll(tokens) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt deleted file mode 100644 index a477782d9f..0000000000 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.feature.referral.domain.converter - -import com.tangem.feature.referral.domain.models.TokenData -import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.Currency.NativeToken -import com.tangem.lib.crypto.models.Currency.NonNativeToken -import com.tangem.utils.converter.Converter - -import javax.inject.Inject - -class TokensConverter @Inject constructor() : Converter { - - override fun convert(value: TokenData): Currency { - return if (value.decimalCount != null && - value.contractAddress != null - ) { - NonNativeToken( - id = value.id, - name = value.name, - symbol = value.symbol, - networkId = value.networkId, - contractAddress = value.contractAddress, - decimalCount = value.decimalCount, - ) - } else { - NativeToken( - id = value.id, - name = value.name, - symbol = value.symbol, - networkId = value.networkId, - ) - } - } -} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt index 9b90eb5b0a..0cd466fed7 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt @@ -6,9 +6,6 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.ReferralInteractorImpl import com.tangem.feature.referral.domain.ReferralRepository -import com.tangem.feature.referral.domain.converter.TokensConverter -import com.tangem.features.tester.api.TesterFeatureToggles -import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.UserWalletManager import dagger.Module import dagger.Provides @@ -24,23 +21,17 @@ class ReferralDomainModule { @ViewModelScoped fun provideReferralInteractor( referralRepository: ReferralRepository, - derivationManager: DerivationManager, userWalletManager: UserWalletManager, - tokensConverter: TokensConverter, derivePublicKeysUseCase: DerivePublicKeysUseCase, getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, - testerFeatureToggles: TesterFeatureToggles, ): ReferralInteractor { return ReferralInteractorImpl( repository = referralRepository, - derivationManager = derivationManager, userWalletManager = userWalletManager, - tokensConverter = tokensConverter, derivePublicKeysUseCase = derivePublicKeysUseCase, getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, - testerFeatureToggles = testerFeatureToggles, ) } } \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt index 5a44a6614d..fce7f71d36 100644 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/api/navigation/SendRouter.kt @@ -9,5 +9,8 @@ interface SendRouter { companion object { const val CRYPTO_CURRENCY_KEY = "send_crypto_currency" const val USER_WALLET_ID_KEY = "send_user_wallet_id" + const val TRANSACTION_ID_KEY = "send_transaction_id" + const val AMOUNT_KEY = "send_amount" + const val DESTINATION_ADDRESS_KEY = "send_destination_address" } } \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 46295abadb..062932fd25 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -45,6 +45,14 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.utils) implementation(projects.core.navigation) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + + /** Common */ + implementation(projects.common) + + /** Libs */ + implementation(projects.libs.crypto) /** Domain modules */ implementation(projects.domain.models) @@ -59,6 +67,8 @@ dependencies { implementation(projects.domain.txhistory.models) implementation(projects.domain.transaction) implementation(projects.domain.card) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) /** Feature modules */ implementation(projects.features.send.api) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index 7cdab91041..1189a49843 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment @@ -41,6 +42,9 @@ internal class SendFragment : ComposeFragment() { @Inject lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase + @Inject + lateinit var analyticsEventsHandler: AnalyticsEventHandler + private val viewModel by viewModels() private val innerSendRouter: InnerSendRouter get() = requireNotNull(router as? InnerSendRouter) { @@ -50,10 +54,14 @@ internal class SendFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(viewModel) + + val isEditingDisabled = arguments?.getString(SendRouter.TRANSACTION_ID_KEY) != null viewModel.setRouter( innerSendRouter, StateRouter( fragmentManager = WeakReference(parentFragmentManager), + isEditingDisabled = isEditingDisabled, + analyticsEventsHandler = analyticsEventsHandler, ), ) listenToQrCode() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt new file mode 100644 index 0000000000..b62f028e0a --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -0,0 +1,136 @@ +package com.tangem.features.send.impl.presentation.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE +import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE +import com.tangem.core.analytics.models.AnalyticsParam.Key.VALIDATION + +/** + * Send screen analytics + */ +internal sealed class SendAnalyticEvents( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Token / Send", event = event, params = params) { + + /** Send screen opened */ + object SendOpened : SendAnalyticEvents(event = "Send Screen Opened") + + /** Next button clicked */ + data class NextButtonClicked(val source: SendScreenSource) : SendAnalyticEvents( + event = "Button - Next", + params = mapOf(SOURCE to source.name), + ) + + /** Back button clicked */ + data class BackButtonClicked(val source: SendScreenSource) : SendAnalyticEvents( + event = "Button - Back", + params = mapOf(SOURCE to source.name), + ) + + // region Address + /** Recipient address screen opened */ + object AddressScreenOpened : SendAnalyticEvents(event = "Address Screen Opened") + + /** Address to send entered */ + data class AddressEntered(val source: EnterAddressSource, val isValid: Boolean) : SendAnalyticEvents( + event = "Address Entered", + params = mapOf( + SOURCE to source.name, + VALIDATION to if (isValid) "Success" else "Fail", + ), + ) + + /** Paste from clipboard button clicked */ + data class PasteButtonClicked(val type: PasteType) : SendAnalyticEvents( + event = "Button - Paste", + params = mapOf( + TYPE to type.name, + ), + ) + + /** Qr Code button clicked */ + object QrCodeButtonClicked : SendAnalyticEvents(event = "Button - QR Code") + // endregion + + // region Amount + /** Amount screen opened */ + object AmountScreenOpened : SendAnalyticEvents(event = "Amount Screen Opened") + + /** Selected currency */ + data class SelectedCurrency(val type: SelectedCurrencyType) : SendAnalyticEvents( + event = "Selected Currency", + params = mapOf(TYPE to type.value), + ) + + /** Currency selector button clicked */ + object SwapCurrencyButtonClicked : SendAnalyticEvents(event = "Button - Swap Currency") + // endregion + + // region Fee + /** Fee screen opened */ + object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") + + /** Selected fee (send after next screen opened) */ + data class SelectedFee(val fee: String) : SendAnalyticEvents( + event = "Fee Selected", + params = mapOf("Commission" to fee), + ) + + /** Custom fee selected */ + object CustomFeeButtonClicked : SendAnalyticEvents(event = "Custom Fee Clicked") + + /** Custom fee edited */ + object GasPriceInserter : SendAnalyticEvents(event = "Gas Price Inserted") + + /** Subtract from amount selector switched (send after next screen opened) */ + object SubtractFromAmount : SendAnalyticEvents(event = "Subtract from Amount") + // endregion + + // region Confirmation + /** Confirmation screen opened */ + object ConfirmationScreenOpened : SendAnalyticEvents(event = "Confirm Screen Opened") + + /** Send transaction button clicked */ + object SendButtonClicked : SendAnalyticEvents(event = "Button - Send") + + /** Screen reopened from confirmation screen */ + data class ScreenReopened(val source: SendScreenSource) : SendAnalyticEvents( + event = "Screen Reopened", + params = mapOf(SOURCE to source.name), + ) + // endregion + + // region Transaction Result + /** Transaction send screen opened */ + object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened") + + /** Share button clicked */ + object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") + + /** Expore button clicked */ + object ExploreButtonClicked : SendAnalyticEvents(event = "Button - Explore") + // endregion +} + +internal enum class SendScreenSource { + Address, + Amount, + Fee, +} + +internal enum class EnterAddressSource { + QRCode, + PasteButton, + RecentAddress, +} + +internal enum class PasteType { + Address, + Memo, +} + +internal enum class SelectedCurrencyType(val value: String) { + Token("Token"), + AppCurrency("App Currency"), +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt new file mode 100644 index 0000000000..e3f5666a1c --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt @@ -0,0 +1,44 @@ +package com.tangem.features.send.impl.presentation.analytics.utils + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.SendUiStateType +import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState +import com.tangem.features.send.impl.presentation.state.fee.FeeType + +internal class SendOnNextScreenAnalyticSender( + private val analyticsEventHandler: AnalyticsEventHandler, +) { + fun send(prevScreen: SendUiStateType, state: SendUiState) { + when (prevScreen) { + SendUiStateType.Fee -> { + val feeState = state.feeState ?: return + val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content + feeSelectorState?.selectedFee?.let { selectedFee -> + val isCustomFeeEdited = feeState.fee?.amount?.value != feeSelectorState.fees.normal.amount.value + if (selectedFee == FeeType.Custom && isCustomFeeEdited) { + analyticsEventHandler.send(SendAnalyticEvents.GasPriceInserter) + } + analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(selectedFee.name)) + } + if (feeState.isSubtract) { + analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount) + } + } + SendUiStateType.Amount -> { + val isFiatSelected = state.amountState?.amountTextField?.isFiatValue ?: return + val selectedCurrency = if (isFiatSelected) { + SelectedCurrencyType.Token + } else { + SelectedCurrencyType.AppCurrency + } + analyticsEventHandler.send( + SendAnalyticEvents.SelectedCurrency(selectedCurrency), + ) + } + else -> Unit + } + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt new file mode 100644 index 0000000000..000f48dea1 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt @@ -0,0 +1,26 @@ +package com.tangem.features.send.impl.presentation.analytics.utils + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource +import com.tangem.features.send.impl.presentation.analytics.PasteType +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents + +internal class SendRecipientAnalyticsSender( + private val analyticsEventHandler: AnalyticsEventHandler, +) { + + fun sendAddressAnalytics(type: EnterAddressSource?, isValidAddress: Boolean) { + type?.let { + if (type == EnterAddressSource.PasteButton) { + analyticsEventHandler.send(SendAnalyticEvents.PasteButtonClicked(PasteType.Address)) + } + analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) + } + } + + fun sendMemoAnalytics(isPasted: Boolean) { + if (isPasted) { + analyticsEventHandler.send(SendAnalyticEvents.PasteButtonClicked(PasteType.Memo)) + } + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index 54eb2f7868..672ceb39d5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -49,4 +49,11 @@ internal sealed class SendAlertState { override val title: TextReference? = null override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title) } + + data class ReserveAmount(val amount: String) : SendAlertState() { + override val title: TextReference = + resourceReference(id = R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)) + override val message: TextReference = + resourceReference(id = R.string.send_notification_invalid_reserve_amount_text) + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index a175478e41..23c9aec225 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -23,7 +23,9 @@ internal class SendEventStateFactory( private val clickIntents: SendClickIntents, private val feeStateFactory: FeeStateFactory, ) { - private val sendTransactionErrorConverter by lazy { SendTransactionAlertConverter(clickIntents) } + private val sendTransactionErrorConverter by lazy(LazyThreadSafetyMode.NONE) { + SendTransactionAlertConverter(clickIntents) + } fun onConsumeEventState(): SendUiState { return currentStateProvider().copy(event = consumedEvent()) @@ -48,10 +50,10 @@ internal class SendEventStateFactory( is TransactionFee.Single -> fee.normal is TransactionFee.Choosable -> { when (feeSelector.selectedFee) { - FeeType.SLOW -> fee.minimum - FeeType.MARKET -> fee.normal - FeeType.FAST -> fee.priority - FeeType.CUSTOM -> return state + FeeType.Slow -> fee.minimum + FeeType.Market -> fee.normal + FeeType.Fast -> fee.priority + FeeType.Custom -> return state } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt index 18e0b4f34c..05d56ca70b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt @@ -4,10 +4,11 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -21,7 +22,7 @@ internal class SendNotificationFactory( private val coinCryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, - private val walletManagersFacade: WalletManagersFacade, + private val currencyChecksRepository: CurrencyChecksRepository, private val clickIntents: SendClickIntents, ) { @@ -30,7 +31,6 @@ internal class SendNotificationFactory( .map { val state = currentStateProvider() val feeState = state.feeState ?: return@map persistentListOf() - val recipientState = state.recipientState ?: return@map persistentListOf() val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO val amountValue = state.amountState?.amountTextField?.value?.toBigDecimalOrNull() ?: BigDecimal.ZERO val sendAmount = if (feeState.isSubtract) feeState.receivedAmountValue else amountValue @@ -39,7 +39,7 @@ internal class SendNotificationFactory( addExceedBalanceNotification(feeAmount, sendAmount) addInvalidAmountNotification(feeState.isSubtract, sendAmount) addMinimumAmountErrorNotification(feeAmount, sendAmount) - addReserveAmountErrorNotification(recipientState.addressTextField.value) + addDustWarningNotification(feeAmount, sendAmount) addTransactionLimitErrorNotification(feeAmount, sendAmount) // warnings addExistentialWarningNotification(feeAmount, sendAmount) @@ -114,26 +114,27 @@ internal class SendNotificationFactory( } } - private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) { - val userWalletId = userWalletProvider().walletId - val cryptoCurrency = cryptoCurrencyStatusProvider().currency - val isAccountFunded = walletManagersFacade.checkIfAccountFunded( - userWalletId, - cryptoCurrency.network, - recipientAddress, - ) - val minimumAmount = walletManagersFacade.getReserveAmount(userWalletId, cryptoCurrency.network) - if (!isAccountFunded && minimumAmount != null && minimumAmount > BigDecimal.ZERO) { - add( - SendNotification.Error.ReserveAmountError( - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = minimumAmount, - cryptoCurrency = cryptoCurrency, - ), - ), - ) - } - } + // todo temporarily disabling notification + // private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) { + // val userWalletId = userWalletProvider().walletId + // val cryptoCurrency = cryptoCurrencyStatusProvider().currency + // val isAccountFunded = currencyChecksRepository.checkIfAccountFunded( + // userWalletId, + // cryptoCurrency.network, + // recipientAddress, + // ) + // val minimumAmount = currencyChecksRepository.getReserveAmount(userWalletId, cryptoCurrency.network) + // if (!isAccountFunded && minimumAmount != null && minimumAmount > BigDecimal.ZERO) { + // add( + // SendNotification.Error.ReserveAmountError( + // BigDecimalFormatter.formatCryptoAmount( + // cryptoAmount = minimumAmount, + // cryptoCurrency = cryptoCurrency, + // ), + // ), + // ) + // } + // } private suspend fun MutableList.addTransactionLimitErrorNotification( feeAmount: BigDecimal, @@ -141,7 +142,7 @@ internal class SendNotificationFactory( ) { val userWalletId = userWalletProvider().walletId val cryptoCurrency = cryptoCurrencyStatusProvider().currency - val utxoLimit = walletManagersFacade.checkUtxoAmountLimit( + val utxoLimit = currencyChecksRepository.checkUtxoAmountLimit( userWalletId = userWalletId, network = cryptoCurrency.network, amount = receivedAmount, @@ -173,7 +174,7 @@ internal class SendNotificationFactory( } else { feeAmount + receivedAmount } - val currencyDeposit = walletManagersFacade.getExistentialDeposit( + val currencyDeposit = currencyChecksRepository.getExistentialDeposit( userWalletId, cryptoCurrency.network, ) @@ -210,6 +211,29 @@ internal class SendNotificationFactory( } } + private suspend fun MutableList.addDustWarningNotification( + feeAmount: BigDecimal, + receivedAmount: BigDecimal, + ) { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val dustValue = currencyChecksRepository.getDustValue( + userWalletProvider().walletId, + cryptoCurrencyStatus.currency.network, + ) + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + if (dustValue != null && !balance.isNullOrZero() && receivedAmount < balance) { + val totalAmount = feeAmount + receivedAmount + val change = balance - totalAmount + val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO + val isShowWarning = totalAmount < dustValue || isChangeLowerThanDust + if (isShowWarning) { + add( + SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), + ) + } + } + } + companion object { private const val CARDANO_MINIMUM = "1" private const val DOGECOIN_MINIMUM = "0.01" diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 17ca733eee..57a1a698ae 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -14,10 +14,8 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet -import com.tangem.features.send.impl.presentation.state.amount.SendAmountCurrencyConverter import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter -import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter @@ -38,29 +36,17 @@ internal class SendStateFactory( private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, ) { - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val amountFieldConverter by lazy { + + private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountFieldConverter( clickIntents = clickIntents, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, appCurrencyProvider = appCurrencyProvider, ) } - private val amountFieldChangeConverter by lazy { - SendAmountFieldChangeConverter( - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - private val amountCurrencyConverter by lazy { - SendAmountCurrencyConverter( - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - private val amountStateConverter by lazy { + private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountStateConverter( appCurrencyProvider = appCurrencyProvider, iconStateConverter = iconStateConverter, @@ -69,20 +55,20 @@ internal class SendStateFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val recipientStateConverter by lazy { + private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendRecipientStateConverter( clickIntents = clickIntents, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val feeStateConverter by lazy { + private val feeStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeStateConverter( appCurrencyProvider = appCurrencyProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val recipientListStateConverter by lazy { + private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendRecipientListConverter( currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, @@ -92,24 +78,34 @@ internal class SendStateFactory( // region UI states fun getInitialState(): SendUiState = SendUiState( clickIntents = clickIntents, - currentState = MutableStateFlow(SendUiStateType.Amount), + currentState = MutableStateFlow(SendUiStateType.None), event = consumedEvent(), + isEditingDisabled = false, + isBalanceHidden = false, ) fun getReadyState(): SendUiState { val state = currentStateProvider() return state.copy( - amountState = state.amountState ?: amountStateConverter.convert(Unit), - recipientState = state.recipientState ?: recipientStateConverter.convert(Unit), + amountState = state.amountState ?: amountStateConverter.convert(""), + recipientState = state.recipientState ?: recipientStateConverter.convert(""), feeState = state.feeState ?: feeStateConverter.convert(Unit), ) } - //endregion - //region amount state clicks - fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) + fun getReadyState(amount: String, destinationAddress: String): SendUiState { + val state = currentStateProvider() + return state.copy( + amountState = state.amountState ?: amountStateConverter.convert(amount), + recipientState = state.recipientState ?: recipientStateConverter.convert(destinationAddress), + feeState = state.feeState ?: feeStateConverter.convert(Unit), + isEditingDisabled = true, + ) + } - fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) + fun getOnHideBalanceState(isBalanceHidden: Boolean): SendUiState { + return currentStateProvider().copy(isBalanceHidden = isBalanceHidden) + } //endregion //region recipient @@ -125,12 +121,13 @@ internal class SendStateFactory( ) } - fun onRecipientAddressValueChange(value: String): SendUiState { + fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() val recipientState = state.recipientState ?: return state return state.copy( recipientState = recipientState.copy( addressTextField = recipientState.addressTextField.copy(value = value), + memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), ), ) } @@ -205,6 +202,20 @@ internal class SendStateFactory( isValidating = false, memoTextField = recipientState.memoTextField?.copy( isError = value.isNotEmpty() && !isValidMemo, + isEnabled = true, + ), + ), + ) + } + + fun getOnXAddressMemoState(): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + return state.copy( + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = "", + isEnabled = false, ), ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt index 14f9cc265d..a35b421a16 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt @@ -38,6 +38,7 @@ internal class SendTransactionAlertConverter( cause = value.ex?.localizedMessage, onConfirmClick = { clickIntents.onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) }, ) + is SendTransactionError.CreateAccountUnderfunded -> SendAlertState.ReserveAmount(value.amount) else -> null } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 16d0b6190a..1828a6b3ed 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -18,6 +18,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import java.math.BigDecimal /** @@ -26,12 +27,14 @@ import java.math.BigDecimal @Immutable internal data class SendUiState( val clickIntents: SendClickIntents, + val isEditingDisabled: Boolean, val amountState: SendStates.AmountState? = null, val recipientState: SendStates.RecipientState? = null, val feeState: SendStates.FeeState? = null, val sendState: SendStates.SendState = SendStates.SendState(), val recipientList: MutableStateFlow> = MutableStateFlow(PagingData.empty()), - val currentState: MutableStateFlow, + val currentState: StateFlow, + val isBalanceHidden: Boolean, val event: StateEvent, ) @@ -99,6 +102,7 @@ internal sealed class SendStates { } enum class SendUiStateType { + None, Amount, Recipient, Fee, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index 4edb445226..ea62b4700d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -1,60 +1,119 @@ package com.tangem.features.send.impl.presentation.state import androidx.fragment.app.FragmentManager +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import java.lang.ref.WeakReference internal class StateRouter( private val fragmentManager: WeakReference, + private val analyticsEventsHandler: AnalyticsEventHandler, + private val isEditingDisabled: Boolean, ) { - var currentState: MutableStateFlow = MutableStateFlow(SendUiStateType.Recipient) - private set + private var mutableCurrentState: MutableStateFlow = MutableStateFlow( + if (isEditingDisabled) { + SendUiStateType.None + } else { + SendUiStateType.Recipient + }, + ) + + val currentState: StateFlow = mutableCurrentState fun popBackStack() { fragmentManager.get()?.popBackStack() } - fun onBackClick() { - when (currentState.value) { - SendUiStateType.Recipient -> popBackStack() - SendUiStateType.Amount -> showRecipient() - SendUiStateType.Fee -> showAmount() - SendUiStateType.Send -> showFee() + fun onBackClick(isSuccess: Boolean = false) { + when { + isSuccess -> popBackStack() + isEditingDisabled -> when (currentState.value) { + SendUiStateType.Send -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showFee() + } + else -> popBackStack() + } + else -> when (currentState.value) { + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Address)) + showRecipient() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount)) + showAmount() + } + SendUiStateType.Send -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showFee() + } + else -> popBackStack() + } } } - fun onNextClick() { + fun onNextClick(): SendUiStateType { + val prevState = currentState.value when (currentState.value) { - SendUiStateType.Recipient -> showAmount() - SendUiStateType.Amount -> showFee() - SendUiStateType.Fee -> showSend() - SendUiStateType.Send -> onBackClick() + SendUiStateType.Recipient -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Amount)) + showAmount() + } + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee)) + showFee() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee)) + showSend() + } + SendUiStateType.Send -> { + onBackClick() + } + else -> popBackStack() } + return prevState } fun onPrevClick() { - when (currentState.value) { - SendUiStateType.Recipient -> popBackStack() - SendUiStateType.Amount -> showRecipient() - SendUiStateType.Fee -> showAmount() - SendUiStateType.Send -> popBackStack() + if (isEditingDisabled) { + popBackStack() + } else { + when (currentState.value) { + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount)) + showRecipient() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showAmount() + } + else -> popBackStack() + } } } fun showAmount() { - currentState.update { SendUiStateType.Amount } + analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened) + mutableCurrentState.update { SendUiStateType.Amount } } fun showRecipient() { - currentState.update { SendUiStateType.Recipient } + analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened) + mutableCurrentState.update { SendUiStateType.Recipient } } fun showFee() { - currentState.update { SendUiStateType.Fee } + analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened) + mutableCurrentState.update { SendUiStateType.Fee } } private fun showSend() { - currentState.update { SendUiStateType.Send } + analyticsEventsHandler.send(SendAnalyticEvents.ConfirmationScreenOpened) + mutableCurrentState.update { SendUiStateType.Send } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt new file mode 100644 index 0000000000..6f56b6aff3 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -0,0 +1,44 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter +import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter +import com.tangem.utils.Provider + +/** + * Factory to produce amount state for [SendUiState] + */ +internal class AmountStateFactory( + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) { + + private val amountFieldChangeConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountFieldChangeConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + private val amountFieldMaxAmountConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountFieldMaxAmountConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + private val amountCurrencyConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountCurrencyConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) + + fun getOnMaxAmountClick(): SendUiState { + return amountFieldMaxAmountConverter.convert(Unit) + } + + fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index 5bda8cc991..df2a63b153 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -22,9 +22,9 @@ internal class SendAmountStateConverter( private val cryptoCurrencyStatusProvider: Provider, private val iconStateConverter: CryptoCurrencyToIconStateConverter, private val sendAmountFieldConverter: SendAmountFieldConverter, -) : Converter { +) : Converter { - override fun convert(value: Unit): SendStates.AmountState { + override fun convert(value: String): SendStates.AmountState { val userWallet = userWalletProvider() val appCurrency = appCurrencyProvider() val status = cryptoCurrencyStatusProvider() @@ -35,7 +35,7 @@ internal class SendAmountStateConverter( walletName = userWallet.name, walletBalance = resourceReference(R.string.send_wallet_balance_format, wrappedList(crypto, fiat)), tokenIconState = iconStateConverter.convert(status), - amountTextField = sendAmountFieldConverter.convert(Unit), + amountTextField = sendAmountFieldConverter.convert(value), isPrimaryButtonEnabled = false, segmentedButtonConfig = persistentListOf( SendAmountSegmentedButtonsConfig( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index c14db17575..7b55e0100d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -28,10 +28,10 @@ internal class FeeConverter( return when (val fees = value.fees) { is TransactionFee.Choosable -> { when (value.selectedFee) { - FeeType.SLOW -> fees.minimum - FeeType.MARKET -> fees.normal - FeeType.FAST -> fees.priority - FeeType.CUSTOM -> convertCustom(value, fees) + FeeType.Slow -> fees.minimum + FeeType.Market -> fees.normal + FeeType.Fast -> fees.priority + FeeType.Custom -> convertCustom(value, fees) } } is TransactionFee.Single -> fees.normal diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt index 244ed17cdd..fd4fa1da94 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt @@ -69,8 +69,8 @@ internal class FeeNotificationFactory( val minimumValue = multipleFees.minimum.amount.value ?: return val customAmount = customFee.firstOrNull() ?: return val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - if (selectedFee == FeeType.CUSTOM && minimumValue > customValue) { - add(SendFeeNotification.Informational.TooLow) + if (selectedFee == FeeType.Custom && minimumValue > customValue) { + add(SendFeeNotification.Warning.TooLow) } } @@ -84,7 +84,7 @@ internal class FeeNotificationFactory( val customAmount = customFee.firstOrNull() ?: return val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) val diff = customValue / highValue - if (selectedFee == FeeType.CUSTOM && diff > FEE_MAX_DIFF) { + if (selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) { add(SendFeeNotification.Warning.TooHigh(diff.toFormattedString(HIGH_FEE_DIFF_DECIMALS))) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt index 9751e61eae..e0cda82bfa 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt @@ -13,7 +13,7 @@ internal sealed class FeeSelectorState { data class Content( val fees: TransactionFee, - val selectedFee: FeeType = FeeType.MARKET, + val selectedFee: FeeType = FeeType.Market, val customValues: ImmutableList = persistentListOf(), ) : FeeSelectorState() @@ -21,8 +21,8 @@ internal sealed class FeeSelectorState { } enum class FeeType { - SLOW, - MARKET, - FAST, - CUSTOM, + Slow, + Market, + Fast, + Custom, } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index fcbfd66705..b8f99a5511 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -27,7 +27,7 @@ internal class FeeStateFactory( private val appCurrencyProvider: Provider, private val isFeeApproximateUseCase: IsFeeApproximateUseCase, ) { - private val customFeeFieldConverter by lazy { + private val customFeeFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeCustomFieldConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, @@ -35,7 +35,7 @@ internal class FeeStateFactory( ) } - val feeConverter by lazy { + val feeConverter by lazy(LazyThreadSafetyMode.NONE) { FeeConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, @@ -191,7 +191,7 @@ internal class FeeStateFactory( val fee = feeConverter.convert(feeSelectorState) val feeValue = fee.amount.value ?: BigDecimal.ZERO - val isNotCustom = feeSelectorState.selectedFee != FeeType.CUSTOM + val isNotCustom = feeSelectorState.selectedFee != FeeType.Custom val isNotEmptyCustom = if (customValue != null) { !customValue.value.parseToBigDecimal(customValue.decimals).isZero() && !isNotCustom } else { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt index d58cb3cd98..f895fb4921 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt @@ -8,22 +8,6 @@ import com.tangem.features.send.impl.R sealed class SendFeeNotification(val config: NotificationConfig) { - sealed class Informational( - val title: TextReference, - val subtitle: TextReference, - ) : SendFeeNotification( - config = NotificationConfig( - title = title, - subtitle = subtitle, - iconResId = R.drawable.ic_alert_circle_24, - ), - ) { - object TooLow : Informational( - title = resourceReference(id = R.string.send_notification_transaction_delay_title), - subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text), - ) - } - sealed class Warning( val title: TextReference, val subtitle: TextReference, @@ -36,6 +20,11 @@ sealed class SendFeeNotification(val config: NotificationConfig) { buttonsState = buttonsState, ), ) { + object TooLow : Warning( + title = resourceReference(id = R.string.send_notification_transaction_delay_title), + subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text), + ) + data class TooHigh( val value: String, ) : Warning( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 4997139e4e..4be7b3acfb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.state.fee.custom +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -43,6 +44,7 @@ internal class EthereumCustomFeeConverter( title = resourceReference(R.string.send_max_fee), footer = resourceReference(R.string.send_max_fee_footer), label = getFeeFormatted(value.amount.value), + keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( value = value.gasPrice.toString(), @@ -55,6 +57,7 @@ internal class EthereumCustomFeeConverter( imeAction = ImeAction.Next, keyboardType = KeyboardType.Number, ), + keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( value = value.gasLimit.toString(), @@ -67,6 +70,7 @@ internal class EthereumCustomFeeConverter( imeAction = ImeAction.Done, keyboardType = KeyboardType.Number, ), + keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 7a6c696c1a..b9bfa076bd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.state.fields +import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrency @@ -21,7 +22,6 @@ internal class SendAmountFieldChangeConverter( val feeState = state.feeState ?: return state if (value.isEmpty()) return state.emptyState() - val cryptoDecimals = amountTextField.cryptoAmount.decimals val fiatDecimals = amountTextField.fiatAmount.decimals @@ -89,10 +89,12 @@ internal class SendAmountFieldChangeConverter( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO + val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) + val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) return if (amountTextField.isFiatValue) { - parseToBigDecimal(amountTextField.fiatAmount.decimals) > currencyFiatAmount + fiatDecimal > currencyFiatAmount || fiatDecimal.isZero() } else { - parseToBigDecimal(amountTextField.cryptoAmount.decimals) > currencyCryptoAmount + cryptoDecimal > currencyCryptoAmount || cryptoDecimal.isZero() } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 25a63f78f8..83701146e2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -1,9 +1,12 @@ package com.tangem.features.send.impl.presentation.state.fields +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import com.tangem.blockchain.extensions.toBigDecimalOrDefault import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.Amount import com.tangem.domain.tokens.model.AmountType @@ -21,20 +24,29 @@ internal class SendAmountFieldConverter( private val clickIntents: SendClickIntents, private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, -) : Converter { +) : Converter { - override fun convert(value: Unit): SendTextField.AmountField { + override fun convert(value: String): SendTextField.AmountField { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val cryptoDecimal = value.toBigDecimalOrDefault() + val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency) + val fiatValue = if (value.isEmpty()) { + "" + } else { + val fiatDecimal = cryptoCurrencyStatus.value.fiatRate?.multiply(cryptoDecimal) ?: BigDecimal.ZERO + fiatDecimal.parseBigDecimal(FIAT_DECIMALS) + } return SendTextField.AmountField( - value = "", - fiatValue = "", + value = value, + fiatValue = fiatValue, onValueChange = clickIntents::onAmountValueChange, keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, + imeAction = ImeAction.Done, keyboardType = KeyboardType.Number, ), + keyboardActions = KeyboardActions(onDone = { clickIntents.onNextClick() }), isFiatValue = false, - cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrencyStatus.currency), + cryptoAmount = cryptoAmount, fiatAmount = getAppCurrencyAmount(appCurrencyProvider()), isError = false, error = TextReference.Res(R.string.swapping_insufficient_funds), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt new file mode 100644 index 0000000000..1a9a4d6def --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -0,0 +1,47 @@ +package com.tangem.features.send.impl.presentation.state.fields + +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero + +internal class SendAmountFieldMaxAmountConverter( + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) : Converter { + + override fun convert(value: Unit): SendUiState { + val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountState = state.amountState ?: return state + val amountTextField = amountState.amountTextField + val feeState = state.feeState ?: return state + + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val decimalCryptoValue = cryptoCurrencyStatus.value.amount + val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount + + if (decimalCryptoValue.isNullOrZero()) return state + + val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + return state.copy( + amountState = amountState.copy( + isPrimaryButtonEnabled = true, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = false, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + ), + ), + feeState = feeState.copy( + isSubtract = true, + ), + ) + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 42b2ac5b0d..97682a9e21 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.state.fields +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference @@ -21,6 +22,7 @@ internal sealed class SendTextField { override val value: String, override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, + val keyboardActions: KeyboardActions, val cryptoAmount: Amount, val fiatAmount: Amount, val isFiatValue: Boolean, @@ -47,12 +49,15 @@ internal sealed class SendTextField { val label: TextReference, val isError: Boolean = false, val error: TextReference? = null, + val disabledText: TextReference, + val isEnabled: Boolean, ) : SendTextField() data class CustomFee( override val value: String, override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, + val keyboardActions: KeyboardActions, val symbol: String?, val decimals: Int, val title: TextReference, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt index bc5cf7a463..ab822de809 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt @@ -11,11 +11,11 @@ import com.tangem.utils.converter.Converter internal class SendRecipientAddressFieldConverter( private val clickIntents: SendClickIntents, -) : Converter { +) : Converter { - override fun convert(value: Unit): SendTextField.RecipientAddress { + override fun convert(value: String): SendTextField.RecipientAddress { return SendTextField.RecipientAddress( - value = "", + value = value, onValueChange = clickIntents::onRecipientAddressValueChange, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt index 45e2f47771..1eeffb3762 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt @@ -28,6 +28,8 @@ internal class SendRecipientMemoFieldConverter( Blockchain.TerraV1.id, Blockchain.TerraV2.id, Blockchain.Stellar.id, + Blockchain.Hedera.id, + Blockchain.Algorand.id, -> convert(R.string.send_extras_hint_memo) else -> null } @@ -44,6 +46,8 @@ internal class SendRecipientMemoFieldConverter( placeholder = resourceReference(R.string.send_optional_field), label = resourceReference(value), error = resourceReference(R.string.send_memo_destination_tag_error), + disabledText = resourceReference(R.string.send_additional_field_already_included), + isEnabled = true, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt index 814d968374..a558d4ded6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt @@ -9,7 +9,7 @@ import com.tangem.utils.converter.Converter internal class SendRecipientStateConverter( private val clickIntents: SendClickIntents, private val cryptoCurrencyStatusProvider: Provider, -) : Converter { +) : Converter { private val addressFieldConverter by lazy { SendRecipientAddressFieldConverter(clickIntents) } private val memoFieldConverter by lazy { @@ -19,9 +19,9 @@ internal class SendRecipientStateConverter( ) } - override fun convert(value: Unit): SendStates.RecipientState { + override fun convert(value: String): SendStates.RecipientState { return SendStates.RecipientState( - addressTextField = addressFieldConverter.convert(Unit), + addressTextField = addressFieldConverter.convert(value), memoTextField = memoFieldConverter.convertOrNull(), network = cryptoCurrencyStatusProvider().currency.network.name, isPrimaryButtonEnabled = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 525578a157..b0462ca18f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -3,6 +3,8 @@ package com.tangem.features.send.impl.presentation.ui import androidx.annotation.StringRes import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -14,6 +16,7 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -24,6 +27,7 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.R import com.tangem.core.ui.components.* +import com.tangem.core.ui.extensions.rememberHapticFeedback import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendUiState @@ -49,9 +53,12 @@ internal fun SendNavigationButtons(uiState: SendUiState) { @Composable private fun SendSecondaryNavigationButton(uiState: SendUiState) { val currentState = uiState.currentState.collectAsState() + val isEditingDisabled = uiState.isEditingDisabled + val isCorrectScreen = currentState.value == SendUiStateType.Amount || currentState.value == SendUiStateType.Fee AnimatedVisibility( - visible = currentState.value == SendUiStateType.Amount || - currentState.value == SendUiStateType.Fee, + visible = !isEditingDisabled && isCorrectScreen, + enter = expandHorizontally(expandFrom = Alignment.End), + exit = shrinkHorizontally(shrinkTowards = Alignment.End), ) { Icon( modifier = Modifier @@ -94,11 +101,12 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier ) { textId -> when { currentState.value == SendUiStateType.Send && !isSuccess -> { + val hapticFeedback = rememberHapticFeedback(state = currentState, onAction = buttonClick) PrimaryButtonIconEnd( text = stringResource(textId), iconResId = R.drawable.ic_tangem_24, enabled = isButtonEnabled, - onClick = buttonClick, + onClick = hapticFeedback, showProgress = isSending, ) } @@ -107,6 +115,7 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier textRes = textId, txUrl = txUrl, onExploreClick = { uiState.clickIntents.onExploreClick(txUrl) }, + onShareClick = uiState.clickIntents::onShareClick, onDoneClick = buttonClick, modifier = Modifier, ) @@ -127,6 +136,7 @@ private fun PrimaryButtonsDone( @StringRes textRes: Int, txUrl: String, onExploreClick: () -> Unit, + onShareClick: () -> Unit, onDoneClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -149,6 +159,7 @@ private fun PrimaryButtonsDone( onClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) context.shareText(txUrl) + onShareClick() }, modifier = Modifier.weight(1f), ) @@ -170,6 +181,7 @@ private fun getButtonData( isSuccess: Boolean, ): Pair Unit> { return when (currentState.value) { + SendUiStateType.None, SendUiStateType.Amount, SendUiStateType.Recipient, SendUiStateType.Fee, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 8fe5c5f53d..88c36f0d4c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -45,6 +45,7 @@ internal fun SendScreen(uiState: SendUiState) { SendUiStateType.Recipient -> R.string.send_recipient_label SendUiStateType.Fee -> R.string.common_fee_selector_title SendUiStateType.Send -> if (!isSuccess) R.string.send_confirm_label else null + else -> null } val iconRes = if (currentState.value == SendUiStateType.Recipient) { R.drawable.ic_qrcode_scan_24 @@ -89,19 +90,21 @@ private fun SendScreenContent( ) { state -> when (state) { SendUiStateType.Amount -> SendAmountContent( - uiState.amountState, - uiState.clickIntents, + amountState = uiState.amountState, + isBalanceHiding = uiState.isBalanceHidden, + clickIntents = uiState.clickIntents, ) SendUiStateType.Recipient -> SendRecipientContent( - uiState.recipientState, - uiState.clickIntents, - recipientList, + uiState = uiState.recipientState, + clickIntents = uiState.clickIntents, + recipientList = recipientList, ) SendUiStateType.Fee -> SendSpeedAndFeeContent( - uiState.feeState, - uiState.clickIntents, + state = uiState.feeState, + clickIntents = uiState.clickIntents, ) SendUiStateType.Send -> SendContent(uiState) + else -> Unit } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index c2ca7a26d8..bd6b8b54e1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -28,10 +28,10 @@ internal fun AmountField(sendField: SendTextField.AmountField, isFiat: Boolean) sendField.value to sendField.fiatValue } - val (primaryAmount, secondaryAmount) = if (!isFiat) { - sendField.cryptoAmount to sendField.fiatAmount - } else { + val (primaryAmount, secondaryAmount) = if (isFiat) { sendField.fiatAmount to sendField.cryptoAmount + } else { + sendField.cryptoAmount to sendField.fiatAmount } AmountTextField( @@ -40,6 +40,7 @@ internal fun AmountField(sendField: SendTextField.AmountField, isFiat: Boolean) symbol = primaryAmount.currencySymbol, onValueChange = sendField.onValueChange, keyboardOptions = sendField.keyboardOptions, + keyboardActions = sendField.keyboardActions, textStyle = TangemTheme.typography.h2.copy( color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt index afd7b30b6e..ce7ba474c6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.amount +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -11,13 +12,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign +import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates @Composable -internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: Modifier = Modifier) { +internal fun AmountFieldContainer( + amountState: SendStates.AmountState, + isBalanceHiding: Boolean, + modifier: Modifier = Modifier, +) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier @@ -37,14 +43,21 @@ internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: modifier = Modifier .padding(top = TangemTheme.dimens.spacing14), ) - Text( - text = amountState.walletBalance.resolveReference(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing2), - ) + + val balance = if (isBalanceHiding) STARS else amountState.walletBalance.resolveReference() + AnimatedContent( + targetState = balance, + label = "Hide Balance Animation", + ) { + Text( + text = it, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing2), + ) + } TokenIcon( state = amountState.tokenIconState, modifier = Modifier diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index ba14154d01..fa0a7445eb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -22,13 +22,17 @@ import com.tangem.features.send.impl.presentation.state.amount.SendAmountSegment import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @Composable -internal fun SendAmountContent(amountState: SendStates.AmountState?, clickIntents: SendClickIntents) { +internal fun SendAmountContent( + amountState: SendStates.AmountState?, + isBalanceHiding: Boolean, + clickIntents: SendClickIntents, +) { if (amountState == null) return Column( modifier = Modifier .background(TangemTheme.colors.background.tertiary), ) { - AmountFieldContainer(amountState = amountState) + AmountFieldContainer(amountState = amountState, isBalanceHiding = isBalanceHiding) Row( modifier = Modifier .padding( @@ -42,6 +46,7 @@ internal fun SendAmountContent(amountState: SendStates.AmountState?, clickIntent .weight(1f) .height(TangemTheme.dimens.size40), config = amountState.segmentedButtonConfig, + showIndication = false, onClick = { clickIntents.onCurrencyChangeClick(it.isFiat) }, ) { SendAmountCurrencyButton(it) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt index a5eba38a01..207b6ee43e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt @@ -20,7 +20,7 @@ internal fun SendCustomFeeEthereum( selectedFee: FeeType, modifier: Modifier = Modifier, ) { - if (selectedFee == FeeType.CUSTOM && customValues.isNotEmpty()) { + if (selectedFee == FeeType.Custom && customValues.isNotEmpty()) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), modifier = modifier, @@ -38,6 +38,7 @@ internal fun SendCustomFeeEthereum( title = value.title, info = value.label, keyboardOptions = value.keyboardOptions, + keyboardActions = value.keyboardActions, onValueChange = value.onValueChange, showDivider = false, modifier = Modifier @@ -54,6 +55,7 @@ internal fun SendCustomFeeEthereum( symbol = value.symbol, onValueChange = value.onValueChange, keyboardOptions = value.keyboardOptions, + keyboardActions = value.keyboardActions, showDivider = false, modifier = Modifier .background( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index b253efe2e5..95a469975a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -15,14 +15,15 @@ import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState +import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.fee.SendFeeNotification import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList private const val FEE_SELECTOR_KEY = "FEE_SELECTOR_KEY" private const val FEE_CUSTOM_KEY = "FEE_CUSTOM_KEY" -@OptIn(ExperimentalFoundationApi::class) @Composable internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: SendClickIntents) { if (state == null) return @@ -36,46 +37,92 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S horizontal = TangemTheme.dimens.spacing16, ), ) { - item( - key = FEE_SELECTOR_KEY, - ) { - SendSpeedSelector( - state = state, - clickIntents = clickIntents, - modifier = Modifier.animateItemPlacement(), - ) - } + feeSelector(state, clickIntents) + topNotifications(notifications) customFee(feeSendState) - notifications(notifications) - subtractButton( - receivedAmount = state.receivedAmount, - isSubtract = state.isSubtract, - isSubtractAvailable = state.isSubtractAvailable, - clickIntents = clickIntents, - ) + middleNotifications(notifications) + subtractButton(state, clickIntents) + bottomNotifications(notifications) } } @OptIn(ExperimentalFoundationApi::class) -internal fun LazyListScope.notifications(configs: ImmutableList, modifier: Modifier = Modifier) { +private fun LazyListScope.feeSelector(state: SendStates.FeeState, clickIntents: SendClickIntents) { + item( + key = FEE_SELECTOR_KEY, + ) { + SendSpeedSelector( + state = state, + clickIntents = clickIntents, + modifier = Modifier.animateItemPlacement(), + ) + } +} + +private fun LazyListScope.topNotifications( + configs: ImmutableList, + modifier: Modifier = Modifier, +) { + notifications( + configs = configs.filter { + it is SendFeeNotification.Error.ExceedsBalance || + it is SendFeeNotification.Warning.NetworkFeeUnreachable + }.toImmutableList(), + modifier = modifier, + ) +} + +private fun LazyListScope.middleNotifications( + configs: ImmutableList, + modifier: Modifier = Modifier, +) { + notifications( + configs = configs.filter { + it is SendFeeNotification.Warning.TooLow || + it is SendFeeNotification.Warning.TooHigh + }.toImmutableList(), + modifier = modifier, + ) +} + +private fun LazyListScope.bottomNotifications( + configs: ImmutableList, + modifier: Modifier = Modifier, +) { + notifications( + configs = configs.filterIsInstance().toImmutableList(), + isLast = true, + modifier = modifier, + ) +} + +@OptIn(ExperimentalFoundationApi::class) +private fun LazyListScope.notifications( + configs: ImmutableList, + modifier: Modifier = Modifier, + isLast: Boolean = false, +) { items( items = configs, key = { it::class.java }, contentType = { it::class.java }, itemContent = { + val bottomPadding = if (isLast) TangemTheme.dimens.spacing12 else TangemTheme.dimens.spacing0 Notification( config = it.config, modifier = modifier - .padding(top = TangemTheme.dimens.spacing12) + .padding( + top = TangemTheme.dimens.spacing12, + bottom = bottomPadding, + ) .animateItemPlacement(), containerColor = when (it) { is SendFeeNotification.Error.ExceedsBalance, is SendFeeNotification.Warning.NetworkFeeUnreachable, - -> TangemTheme.colors.background.primary + -> TangemTheme.colors.background.action else -> TangemTheme.colors.button.disabled }, iconTint = when (it) { - is SendFeeNotification.Informational -> TangemTheme.colors.icon.accent is SendFeeNotification.Error.ExceedsBalance -> { if (it.config.buttonsState == null) { TangemTheme.colors.icon.warning @@ -116,20 +163,30 @@ internal fun LazyListScope.customFee(feeSendState: FeeSelectorState, modifier: M @OptIn(ExperimentalFoundationApi::class) internal fun LazyListScope.subtractButton( - receivedAmount: String, - isSubtract: Boolean, - isSubtractAvailable: Boolean, + state: SendStates.FeeState, clickIntents: SendClickIntents, modifier: Modifier = Modifier, ) { + val receivedAmount = state.receivedAmount + val isSubtract = state.isSubtract + val isSubtractAvailable = state.isSubtractAvailable + val feeSendState = state.feeSelectorState if (isSubtractAvailable) { item { + val feeStateContent = feeSendState as? FeeSelectorState.Content + val isCustomAvailable = feeStateContent?.customValues.isNullOrEmpty().not() + val isCustomSelected = feeStateContent?.selectedFee == FeeType.Custom + val topPadding = if (isCustomSelected && isCustomAvailable) { + TangemTheme.dimens.spacing12 + } else { + TangemTheme.dimens.spacing20 + } SendSpeedSubtract( receivingAmount = receivedAmount, isSubtract = isSubtract, onSelectClick = clickIntents::onSubtractSelect, modifier = modifier - .padding(vertical = TangemTheme.dimens.spacing12) + .padding(top = topPadding) .animateItemPlacement(), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt index db73ae5c7e..0bac355bb7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt @@ -4,6 +4,9 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -39,6 +42,7 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.features.send.impl.presentation.state.fee.SendFeeNotification import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import java.math.BigDecimal @@ -84,8 +88,8 @@ internal fun SendSpeedSelector( amount = getCryptoReference(minimumAmount, state.isFeeApproximate), fiatAmount = getFiatReference(minimumAmount, state.rate, state.appCurrency), symbolLength = minimumAmount.currencySymbol.length, - isSelected = isSelected == FeeType.SLOW, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.SLOW) }, + isSelected = isSelected == FeeType.Slow, + onSelect = { clickIntents.onFeeSelectorClick(FeeType.Slow) }, ) val normalAmount = fees.normal.amount SendSpeedSelectorItem( @@ -94,8 +98,8 @@ internal fun SendSpeedSelector( amount = getCryptoReference(normalAmount, state.isFeeApproximate), fiatAmount = getFiatReference(normalAmount, state.rate, state.appCurrency), symbolLength = normalAmount.currencySymbol.length, - isSelected = isSelected == FeeType.MARKET, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) }, + isSelected = isSelected == FeeType.Market, + onSelect = { clickIntents.onFeeSelectorClick(FeeType.Market) }, ) val priorityAmount = fees.priority.amount SendSpeedSelectorItem( @@ -104,20 +108,25 @@ internal fun SendSpeedSelector( amount = getCryptoReference(priorityAmount, state.isFeeApproximate), fiatAmount = getFiatReference(priorityAmount, state.rate, state.appCurrency), symbolLength = priorityAmount.currencySymbol.length, - isSelected = isSelected == FeeType.FAST, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.FAST) }, + isSelected = isSelected == FeeType.Fast, + onSelect = { clickIntents.onFeeSelectorClick(FeeType.Fast) }, showDivider = fees.normal is Fee.Ethereum, ) AnimatedVisibility( visible = fees.normal is Fee.Ethereum, label = "Custom fee appearance animation", ) { + val showWarning = state.notifications.any { + it is SendFeeNotification.Warning.TooHigh || + it is SendFeeNotification.Warning.TooLow + } SendSpeedSelectorItem( titleRes = R.string.common_fee_selector_option_custom, iconRes = R.drawable.ic_edit_24, - isSelected = isSelected == FeeType.CUSTOM, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.CUSTOM) }, + isSelected = isSelected == FeeType.Custom, + onSelect = { clickIntents.onFeeSelectorClick(FeeType.Custom) }, showDivider = fees.normal !is Fee.Ethereum, + showWarning = showWarning, ) } } @@ -130,7 +139,7 @@ internal fun SendSpeedSelector( amount = getCryptoReference(normalAmount, state.isFeeApproximate), fiatAmount = getFiatReference(normalAmount, state.rate, state.appCurrency), symbolLength = normalAmount.currencySymbol.length, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) }, + onSelect = { clickIntents.onFeeSelectorClick(FeeType.Market) }, showDivider = false, ) } @@ -224,6 +233,7 @@ private fun SendSpeedSelectorItem( symbolLength: Int? = null, isSelected: Boolean = false, showDivider: Boolean = true, + showWarning: Boolean = false, ) { val iconTint by animateColorAsState( targetValue = if (isSelected) { @@ -259,7 +269,10 @@ private fun SendSpeedSelectorItem( symbolLength = symbolLength, textStyle = textStyle, ) + } else { + SpacerWMax() } + WarningIcon(showWarning = showWarning) } if (showDivider) { Box( @@ -340,6 +353,26 @@ private fun RowScope.SelectorValueContent( ) } +@Composable +private fun WarningIcon(showWarning: Boolean = false) { + AnimatedVisibility( + visible = showWarning, + label = "Custom fee warning indicator", + enter = fadeIn(), + exit = fadeOut(), + ) { + Image( + painter = painterResource(R.drawable.ic_alert_triangle_20), + contentDescription = null, + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing12, + horizontal = TangemTheme.dimens.spacing14, + ), + ) + } +} + //region preview @Preview @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index d2741dffcc..c80581a8f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.ui.common.FooterContainer @@ -58,7 +59,7 @@ internal fun SendRecipientContent( title = address.label, placeholder = address.placeholder, onValueChange = address.onValueChange, - onPasteClick = clickIntents::onRecipientAddressValueChange, + onPasteClick = { clickIntents.onRecipientAddressValueChange(it, EnterAddressSource.PasteButton) }, isError = isError, isLoading = isValidating, error = address.error, @@ -73,16 +74,18 @@ internal fun SendRecipientContent( } uiState.memoTextField?.let { memoField -> item(key = MEMO_FIELD_KEY) { + val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText TextFieldWithPaste( value = memoField.value, label = memoField.label, - placeholder = memoField.placeholder, + placeholder = placeholder, footer = stringResource(R.string.send_recipient_memo_footer), onValueChange = memoField.onValueChange, - onPasteClick = clickIntents::onRecipientMemoValueChange, + onPasteClick = { clickIntents.onRecipientMemoValueChange(it, isPasted = true) }, modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), isError = memoField.isError, error = memoField.error, + isReadOnly = !memoField.isEnabled, ) } } @@ -163,7 +166,9 @@ private fun LazyListScope.recipientListItem( }, ) .background(TangemTheme.colors.background.action), - onClick = { clickIntents.onRecipientAddressValueChange(title) }, + onClick = { + clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) + }, ) } } @@ -201,7 +206,7 @@ private fun RecipientWalletListItem( ListItemWithIcon( title = wallet.title.resolveReference(), subtitle = wallet.subtitle.resolveReference(), - onClick = { clickIntents.onRecipientAddressValueChange(title) }, + onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, ) } if (!item.isWalletsOnly) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 758fef6f57..6109c5ff50 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -1,15 +1,14 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.CenterEnd import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import com.tangem.core.ui.components.fields.SimpleTextField +import com.tangem.core.ui.components.inputrow.inner.CrossIcon import com.tangem.core.ui.components.inputrow.inner.PasteButton import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference @@ -27,46 +26,56 @@ internal fun TextFieldWithPaste( footer: String? = null, error: TextReference? = null, isError: Boolean = false, + isReadOnly: Boolean = false, ) { - val (title, color) = if (isError && error != null) { - error to TangemTheme.colors.text.warning - } else { - label to TangemTheme.colors.text.secondary + val (title, color) = when { + isError && error != null -> error to TangemTheme.colors.text.warning + isReadOnly -> label to TangemTheme.colors.text.disabled + else -> label to TangemTheme.colors.text.secondary } FooterContainer(modifier, footer) { - Row( + Box( modifier = Modifier .background( color = TangemTheme.colors.background.action, shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(TangemTheme.dimens.spacing12), - ) { - Text( - text = title.resolveReference(), - style = TangemTheme.typography.body2, - color = color, ) - SimpleTextField( - value = value, - placeholder = placeholder, - onValueChange = onValueChange, + .padding(end = TangemTheme.dimens.spacing16), + ) { + Row { + Column( modifier = Modifier - .fillMaxWidth() - .padding(top = TangemTheme.dimens.spacing6), + .weight(1f) + .padding(TangemTheme.dimens.spacing12), + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.body2, + color = color, + ) + SimpleTextField( + value = value, + placeholder = placeholder, + onValueChange = onValueChange, + readOnly = isReadOnly, + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens.spacing6), + ) + } + CrossIcon( + onClick = onPasteClick, + modifier = Modifier + .align(CenterVertically), + ) + } + if (!isReadOnly) { + PasteButton( + isPasteButtonVisible = value.isBlank(), + onClick = onPasteClick, + modifier = Modifier.align(CenterEnd), ) } - PasteButton( - isPasteButtonVisible = value.isBlank(), - onClick = onPasteClick, - modifier = Modifier - .align(CenterVertically) - .padding(end = TangemTheme.dimens.spacing16), - ) } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index d014f71caa..6d335ee554 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.send +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -17,6 +18,7 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle +import com.tangem.common.Strings import com.tangem.core.ui.components.inputrow.InputRowDefault import com.tangem.core.ui.components.inputrow.InputRowImage import com.tangem.core.ui.components.inputrow.InputRowRecipientDefault @@ -65,18 +67,21 @@ internal fun SendContent(uiState: SendUiState) { FromWallet( walletName = amountState.walletName, walletBalance = amountState.walletBalance.resolveReference(), + isBalanceHidden = uiState.isBalanceHidden, ) } - AmountBlock( - amountState = amountState, - isSuccess = isSuccess, - onClick = uiState.clickIntents::showAmount, - ) RecipientBlock( recipientState = recipientState, isSuccess = isSuccess, + isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showRecipient, ) + AmountBlock( + amountState = amountState, + isSuccess = isSuccess, + isEditingDisabled = uiState.isEditingDisabled, + onClick = uiState.clickIntents::showAmount, + ) FeeBlock( feeState = feeState, isSuccess = isSuccess, @@ -89,7 +94,7 @@ internal fun SendContent(uiState: SendUiState) { } @Composable -private fun FromWallet(walletName: String, walletBalance: String) { +private fun FromWallet(walletName: String, walletBalance: String, isBalanceHidden: Boolean) { Column( modifier = Modifier .fillMaxWidth() @@ -108,20 +113,31 @@ private fun FromWallet(walletName: String, walletBalance: String) { style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, ) - Text( - text = walletBalance, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - modifier = Modifier - .padding( - top = TangemTheme.dimens.spacing8, - ), - ) + val balance = if (isBalanceHidden) Strings.STARS else walletBalance + AnimatedContent( + targetState = balance, + label = "Hide Balance Animation", + ) { + Text( + text = it, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing8, + ), + ) + } } } @Composable -private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: Boolean, onClick: () -> Unit) { +private fun AmountBlock( + amountState: SendStates.AmountState, + isSuccess: Boolean, + isEditingDisabled: Boolean, + onClick: () -> Unit, +) { val amount = amountState.amountTextField val cryptoAmount = formatCryptoAmount( @@ -134,6 +150,11 @@ private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: Boolean, fiatCurrencyCode = (amount.fiatAmount.type as AmountType.FiatType).code, fiatCurrencySymbol = amount.fiatAmount.currencySymbol, ) + val backgroundColor = if (isEditingDisabled) { + TangemTheme.colors.button.disabled + } else { + TangemTheme.colors.background.action + } InputRowImage( title = TextReference.Res(R.string.send_amount_label), subtitle = TextReference.Str(cryptoAmount), @@ -142,21 +163,31 @@ private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: Boolean, showNetworkIcon = true, modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess) { onClick() }, + .background(backgroundColor) + .clickable(enabled = !isSuccess && !isEditingDisabled) { onClick() }, ) } @Composable -private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess: Boolean, onClick: () -> Unit) { +private fun RecipientBlock( + recipientState: SendStates.RecipientState, + isSuccess: Boolean, + isEditingDisabled: Boolean, + onClick: () -> Unit, +) { val address = recipientState.addressTextField val memo = recipientState.memoTextField + val backgroundColor = if (isEditingDisabled) { + TangemTheme.colors.button.disabled + } else { + TangemTheme.colors.background.action + } Column( modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess) { onClick() }, + .background(backgroundColor) + .clickable(enabled = !isSuccess && !isEditingDisabled) { onClick() }, ) { val showMemo = memo != null && memo.value.isNotBlank() InputRowRecipientDefault( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index cd3c448c3f..18252b0ed4 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -2,10 +2,11 @@ package com.tangem.features.send.impl.presentation.viewmodel import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.fee.FeeType @Suppress("TooManyFunctions") -interface SendClickIntents { +internal interface SendClickIntents { fun popBackStack() @@ -30,9 +31,9 @@ interface SendClickIntents { // endregion // region Recipient - fun onRecipientAddressValueChange(value: String) + fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null) - fun onRecipientMemoValueChange(value: String) + fun onRecipientMemoValueChange(value: String, isPasted: Boolean = false) // endregion // region Fee @@ -56,6 +57,8 @@ interface SendClickIntents { fun onExploreClick(txUrl: String) + fun onShareClick() + fun onAmountReduceClick(reducedAmount: String) fun onAmountReduceIgnoreClick() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index b02ffb58d7..fa912092d9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -7,19 +7,18 @@ import androidx.lifecycle.* import androidx.paging.PagingData import arrow.core.Either import arrow.core.getOrElse -import com.tangem.blockchain.blockchains.xrp.XrpAddressService -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.* import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.CreateTransactionUseCase @@ -36,12 +35,19 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource +import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScreenAnalyticSender +import com.tangem.features.send.impl.presentation.analytics.utils.SendRecipientAnalyticsSender import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory import com.tangem.features.send.impl.presentation.state.fee.FeeNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -74,6 +80,9 @@ internal class SendViewModel @Inject constructor( private val walletManagersFacade: WalletManagersFacade, private val reduxStateHolder: ReduxStateHolder, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, + currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -88,6 +97,10 @@ internal class SendViewModel @Inject constructor( private val cryptoCurrency: CryptoCurrency = savedStateHandle[SendRouter.CRYPTO_CURRENCY_KEY] ?: error("This screen can't open without `CryptoCurrency`") + private val transactionId: String? = savedStateHandle[SendRouter.TRANSACTION_ID_KEY] + private val amount: String? = savedStateHandle[SendRouter.AMOUNT_KEY] + private val destinationAddress: String? = savedStateHandle[SendRouter.DESTINATION_ADDRESS_KEY] + private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() private var innerRouter: InnerSendRouter by Delegates.notNull() @@ -103,6 +116,11 @@ internal class SendViewModel @Inject constructor( getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase, ) + private val amountStateFactory = AmountStateFactory( + currentStateProvider = Provider { uiState }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + ) + private val feeStateFactory = FeeStateFactory( clickIntents = this, currentStateProvider = Provider { uiState }, @@ -132,10 +150,16 @@ internal class SendViewModel @Inject constructor( coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, - walletManagersFacade = walletManagersFacade, + currencyChecksRepository = currencyChecksRepository, clickIntents = this, ) + private val sendOnNextScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { + SendOnNextScreenAnalyticSender(analyticsEventHandler) + } + private val sendRecipientAnalyticsSender by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientAnalyticsSender(analyticsEventHandler) + } // todo convert to StateFlow var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState()) private set @@ -158,6 +182,8 @@ internal class SendViewModel @Inject constructor( override fun onCreate(owner: LifecycleOwner) { subscribeOnCurrencyStatusUpdates(owner) onStateActive() + subscribeOnBalanceHidden(owner) + analyticsEventHandler.send(SendAnalyticEvents.SendOpened) } fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) { @@ -184,6 +210,17 @@ internal class SendViewModel @Inject constructor( } } + private fun subscribeOnBalanceHidden(owner: LifecycleOwner) { + getBalanceHidingSettingsUseCase() + .flowWithLifecycle(owner.lifecycle) + .conflate() + .distinctUntilChanged() + .onEach { + uiState = stateFactory.getOnHideBalanceState(isBalanceHidden = it.isBalanceHidden) + } + .launchIn(viewModelScope) + } + private fun getCurrenciesStatusUpdates(owner: LifecycleOwner, wallet: UserWallet) { val isSingleWallet = wallet.scanResponse.walletData?.token != null && !wallet.isMultiCurrency @@ -192,11 +229,10 @@ internal class SendViewModel @Inject constructor( .flowWithLifecycle(owner.lifecycle) .onEach { currencyStatus -> currencyStatus.onRight { - cryptoCurrencyStatus = it - coinCryptoCurrencyStatus = it - getWalletsAndRecent() - uiState = stateFactory.getReadyState() - updateNotifications() + onDataLoaded( + currencyStatus = it, + coinCurrencyStatus = it, + ) } } .flowOn(dispatchers.main) @@ -208,10 +244,10 @@ internal class SendViewModel @Inject constructor( flow2 = getCurrencyStatusUpdates(isSingleWallet = isSingleWallet), ) { coinStatus, currencyStatus -> if (coinStatus.isRight() && currencyStatus.isRight()) { - coinStatus.onRight { coinCryptoCurrencyStatus = it } - currencyStatus.onRight { cryptoCurrencyStatus = it } - getWalletsAndRecent() - uiState = stateFactory.getReadyState() + onDataLoaded( + currencyStatus = currencyStatus.getOrElse { error("Currency status is unreachable") }, + coinCurrencyStatus = coinStatus.getOrElse { error("Coin status is unreachable") }, + ) } }.flowWithLifecycle(owner.lifecycle) .flowOn(dispatchers.main) @@ -245,6 +281,21 @@ internal class SendViewModel @Inject constructor( ) } + private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, coinCurrencyStatus: CryptoCurrencyStatus) { + cryptoCurrencyStatus = currencyStatus + coinCryptoCurrencyStatus = coinCurrencyStatus + + if (transactionId != null && amount != null && destinationAddress != null) { + uiState = stateFactory.getReadyState(amount, destinationAddress) + stateRouter.showFee() + } else { + getWalletsAndRecent() + uiState = stateFactory.getReadyState() + stateRouter.showRecipient() + } + updateNotifications() + } + private fun getWalletsAndRecent() { combine( flow = getUserWallets().conflate(), @@ -281,10 +332,12 @@ internal class SendViewModel @Inject constructor( userWalletId = wallet.walletId, network = walletCurrency.network, ) - return@fold AvailableWallet( - name = wallet.name, - address = addresses.first().value, - ) + return@fold addresses.firstOrNull()?.let { + AvailableWallet( + name = wallet.name, + address = it.value, + ) + } }, ifLeft = { null }, ) @@ -352,11 +405,18 @@ internal class SendViewModel @Inject constructor( // region screen state navigation override fun popBackStack() = stateRouter.popBackStack() - override fun onBackClick() = stateRouter.onBackClick() - override fun onNextClick() = stateRouter.onNextClick() + override fun onBackClick() = stateRouter.onBackClick(uiState.sendState.isSuccess) + override fun onNextClick() { + val prevScreen = stateRouter.onNextClick() + sendOnNextScreenAnalyticSender.send(prevScreen, uiState) + } + override fun onPrevClick() = stateRouter.onPrevClick() - override fun onQrCodeScanClick() = innerRouter.openQrCodeScanner(cryptoCurrency.network.name) + override fun onQrCodeScanClick() { + analyticsEventHandler.send(SendAnalyticEvents.QrCodeButtonClicked) + innerRouter.openQrCodeScanner(cryptoCurrency.network.name) + } override fun onFailedTxEmailClick(errorMessage: String) { reduxStateHolder.dispatch(LegacyAction.SendEmailTransactionFailed(errorMessage)) @@ -368,24 +428,16 @@ internal class SendViewModel @Inject constructor( // region amount state clicks override fun onCurrencyChangeClick(isFiat: Boolean) { - uiState = stateFactory.getOnCurrencyChangedState(isFiat) + analyticsEventHandler.send(SendAnalyticEvents.SwapCurrencyButtonClicked) + uiState = amountStateFactory.getOnCurrencyChangedState(isFiat) } override fun onAmountValueChange(value: String) { - uiState = stateFactory.getOnAmountValueChange(value) + uiState = amountStateFactory.getOnAmountValueChange(value) } override fun onMaxValueClick() { - val amountState = uiState.amountState ?: return - val amountTextField = amountState.amountTextField - val (amount, decimals) = if (amountTextField.isFiatValue) { - cryptoCurrencyStatus.value.fiatAmount to amountTextField.fiatAmount.decimals - } else { - cryptoCurrencyStatus.value.amount to amountTextField.cryptoAmount.decimals - } - if (amount != null && !amount.isZero()) { - onAmountValueChange(amount.parseBigDecimal(decimals)) - } + uiState = amountStateFactory.getOnMaxAmountClick() } // endregion @@ -394,7 +446,7 @@ internal class SendViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { parseSharedAddressUseCase(address, cryptoCurrency.network).fold( ifRight = { parsedCode -> - onRecipientAddressValueChange(parsedCode.address) + onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) parsedCode.amount?.let { onAmountValueChange(it.toPlainString()) } parsedCode.memo?.let { onRecipientMemoValueChange(it) } }, @@ -405,24 +457,26 @@ internal class SendViewModel @Inject constructor( }.saveIn(qrScannerJobHolder) } - override fun onRecipientAddressValueChange(value: String) { - uiState = stateFactory.onRecipientAddressValueChange(value) + override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch(dispatchers.main) { - uiState = stateFactory.getOnRecipientAddressValidationStarted() if (!checkIfXrpAddressValue(value)) { + uiState = stateFactory.onRecipientAddressValueChange(value) + uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) + sendRecipientAnalyticsSender.sendAddressAnalytics(type, isValidAddress) } }.saveIn(addressValidationJobHolder) } - override fun onRecipientMemoValueChange(value: String) { - uiState = stateFactory.getOnRecipientMemoValueChange(value) + override fun onRecipientMemoValueChange(value: String, isPasted: Boolean) { viewModelScope.launch(dispatchers.main) { - uiState = stateFactory.getOnRecipientAddressValidationStarted() if (!checkIfXrpAddressValue(value)) { + uiState = stateFactory.getOnRecipientMemoValueChange(value) + uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) + sendRecipientAnalyticsSender.sendMemoAnalytics(isPasted) } }.saveIn(addressValidationJobHolder) } @@ -435,16 +489,14 @@ internal class SendViewModel @Inject constructor( ).getOrElse { false } } - private fun checkIfXrpAddressValue(value: String): Boolean { - if (cryptoCurrency.network.id.value == Blockchain.XRP.id && value.firstOrNull() == XRP_X_ADDRESS) { - viewModelScope.launch(dispatchers.io) { - val result = XrpAddressService.decodeXAddress(value) - onRecipientAddressValueChange(result?.address.orEmpty()) - onRecipientMemoValueChange(result?.destinationTag.toString()) - } - return true - } - return false + private suspend fun checkIfXrpAddressValue(value: String): Boolean { + return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> + uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true) + uiState = stateFactory.getOnXAddressMemoState() + val isValidAddress = validateAddress(decodedAddress.address) + uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) + true + } ?: false } // endregion @@ -454,6 +506,9 @@ internal class SendViewModel @Inject constructor( override fun onFeeSelectorClick(feeType: FeeType) { uiState = feeStateFactory.onFeeSelectedState(feeType) updateFeeNotifications() + if (feeType == FeeType.Custom) { + analyticsEventHandler.send(SendAnalyticEvents.CustomFeeButtonClicked) + } } override fun onCustomFeeValueChange(index: Int, value: String) { @@ -514,18 +569,35 @@ internal class SendViewModel @Inject constructor( onCheckFeeUpdate() } sendIdleTimer = System.currentTimeMillis() + analyticsEventHandler.send(SendAnalyticEvents.SendButtonClicked) } - override fun showAmount() = stateRouter.showAmount() + override fun showAmount() { + stateRouter.showAmount() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount)) + } - override fun showRecipient() = stateRouter.showRecipient() + override fun showRecipient() { + stateRouter.showRecipient() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address)) + } - override fun showFee() = stateRouter.showFee() + override fun showFee() { + stateRouter.showFee() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) + } - override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl) + override fun onExploreClick(txUrl: String) { + analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked) + innerRouter.openUrl(txUrl) + } + + override fun onShareClick() { + analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) + } override fun onAmountReduceClick(reducedAmount: String) { - uiState = stateFactory.getOnAmountValueChange(reducedAmount) + uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) uiState = sendNotificationFactory.dismissHighFeeWarningState() loadFee() } @@ -588,6 +660,7 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) uiState = stateFactory.getTransactionSendState(txData) scheduleBalanceUpdate() + analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) }, ) } @@ -645,7 +718,6 @@ internal class SendViewModel @Inject constructor( // endregion companion object { - private const val XRP_X_ADDRESS = 'X' private const val CHECK_FEE_UPDATE_DELAY = 60_000L private const val BALANCE_UPDATE_DELAY = 10_000L } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 6bcbabd302..5ce1371a15 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -23,7 +23,6 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.UserWalletId @@ -298,14 +297,6 @@ internal class DefaultSwapRepository @Inject constructor( } } - override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { - return walletManagersFacade.getExistentialDeposit(userWalletId, network) - } - - override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? { - return walletManagersFacade.getDustValue(userWalletId, network) - } - private fun parseTxDetails(txDetailsJson: String): TxDetails? { return try { txDetailsMoshiAdapter.fromJson(txDetailsJson) diff --git a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index 340162c44c..8fcb79ae26 100644 --- a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -2,7 +2,6 @@ package com.tangem.feature.swap.domain.api import arrow.core.Either import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.domain.* @@ -69,8 +68,4 @@ interface SwapRepository { ): Either fun getNativeTokenForNetwork(networkId: String): CryptoCurrency - - suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? - - suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? } \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 264f39582b..1fae322ed1 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -42,10 +42,12 @@ sealed class PriceImpact { abstract val value: Float - fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt() data class Empty(override val value: Float = 0f) : PriceImpact() + data class Value(override val value: Float) : PriceImpact() + fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt() + companion object { private const val HUNDRED_PERCENTS = 100 } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 0928f9385a..0bdde7ae48 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -14,6 +14,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.SendTransactionError @@ -58,6 +59,7 @@ internal class SwapInteractorImpl @Inject constructor( private val quotesRepository: QuotesRepository, private val dispatcher: CoroutineDispatcherProvider, private val swapTransactionRepository: SwapTransactionRepository, + private val currencyChecksRepository: CurrencyChecksRepository, private val appCurrencyRepository: AppCurrencyRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, ) : SwapInteractor { @@ -380,7 +382,7 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, fromToken: CryptoCurrency, ) { - val existentialDeposit = repository.getExistentialDeposit(userWalletId, fromToken.network) + val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, fromToken.network) if (existentialDeposit != null) { val nativeBalance = userWalletManager.getNativeTokenBalance( fromToken.network.backendId, @@ -404,7 +406,7 @@ internal class SwapInteractorImpl @Inject constructor( is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue } - val dust = repository.getDustValue(userWalletId, fromTokenStatus.currency.network) + val dust = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO if (dust != null && !balance.isNullOrZero() && @@ -664,6 +666,7 @@ internal class SwapInteractorImpl @Inject constructor( Fee.Aptos( amount = feeAmount, gasUnitPrice = fee.feeValue.toLong() / fee.gasLimit, + gasLimit = fee.gasLimit.toLong(), ) } else -> Fee.Common(feeAmount) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index cbf8cd6d81..b1fef42180 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -7,6 +7,7 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.transaction.TransactionRepository @@ -42,6 +43,7 @@ class SwapDomainModule { quotesRepository: QuotesRepository, swapTransactionRepository: SwapTransactionRepository, appCurrencyRepository: AppCurrencyRepository, + currencyChecksRepository: CurrencyChecksRepository, walletManagersFacade: WalletManagersFacade, coroutineDispatcherProvider: CoroutineDispatcherProvider, initialToCurrencyResolver: InitialToCurrencyResolver, @@ -59,6 +61,7 @@ class SwapDomainModule { dispatcher = coroutineDispatcherProvider, swapTransactionRepository = swapTransactionRepository, appCurrencyRepository = appCurrencyRepository, + currencyChecksRepository = currencyChecksRepository, initialToCurrencyResolver = initialToCurrencyResolver, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 4e77ff7dc8..71df4eb5d3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -84,6 +84,7 @@ sealed interface TransactionCardType { ) : TransactionCardType data class ReadOnly( + val showWarning: Boolean = false, val onWarningClick: (() -> Unit)? = null, @StringRes override val headerResId: Int = R.string.swapping_to_title, ) : TransactionCardType @@ -105,12 +106,12 @@ sealed interface SwapWarning { object InsufficientFunds : SwapWarning data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning data class GenericWarning( + val title: TextReference? = null, val message: TextReference? = null, val type: GenericWarningType = GenericWarningType.OTHER, val shouldWrapMessage: Boolean = false, val onClick: () -> Unit, ) : SwapWarning - // data class RateExpired(val onClick: () -> Unit) : SwapWarning data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index 52fffa4053..abeec20c38 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -24,4 +24,5 @@ data class UiActions( val onPolicyClick: (String) -> Unit, val onTosClick: (String) -> Unit, val onReceiveCardWarningClick: () -> Unit, + val onFeeReadMoreClick: (String) -> Unit, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseFeeBottomSheetConfig.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseFeeBottomSheetConfig.kt index fddf5f557e..f54845cd51 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseFeeBottomSheetConfig.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseFeeBottomSheetConfig.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.models.states import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.TextReference import com.tangem.feature.swap.domain.models.ui.FeeType import kotlinx.collections.immutable.ImmutableList @@ -8,4 +9,7 @@ data class ChooseFeeBottomSheetConfig( val selectedFee: FeeType, val onSelectFeeType: (FeeType) -> Unit, val feeItems: ImmutableList, + val readMoreUrl: String, + val readMore: TextReference, + val onReadMoreClick: (String) -> Unit, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt index d0f47a62a2..4cef8254c1 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt @@ -3,18 +3,23 @@ package com.tangem.feature.swap.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.ClickableText import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.ui.FeeType @@ -33,7 +38,9 @@ fun ChooseFeeBottomSheet(config: TangemBottomSheetConfig) { @Composable private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) { Column( - modifier = Modifier.background(TangemTheme.colors.background.primary), + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .padding(bottom = TangemTheme.dimens.spacing8), ) { Text( text = stringResource(R.string.common_fee_selector_title), @@ -53,21 +60,47 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) { ) { FeeItemsBlock(content) } - Text( - text = stringResource(R.string.common_fee_selector_footer), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing8, - horizontal = TangemTheme.dimens.spacing16, - ) - .align(Alignment.CenterHorizontally), - textAlign = TextAlign.Start, + FooterBlock( + readMore = content.readMore, + readMoreUrl = content.readMoreUrl, + onReadMoreClick = content.onReadMoreClick, ) } } +@Composable +private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMoreClick: (String) -> Unit) { + val linkText = readMore.resolveReference() + val fullString = stringResource(R.string.common_fee_selector_footer, linkText) + val linkTextPosition = fullString.length - linkText.length + val annotatedString = buildAnnotatedString { + withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { + append(fullString.substring(0, linkTextPosition)) + } + withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { + append(fullString.substring(linkTextPosition, fullString.length)) + } + } + + val click = { i: Int -> + val readMoreStyle = requireNotNull(annotatedString.spanStyles.getOrNull(1)) + if (i in readMoreStyle.start..readMoreStyle.end) { + onReadMoreClick(readMoreUrl) + } + } + + ClickableText( + text = annotatedString, + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing16, + ), + style = TangemTheme.typography.caption2.copy(textAlign = TextAlign.Start), + onClick = click, + ) +} + @Composable private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) { content.feeItems.forEach { feeItem -> @@ -129,6 +162,9 @@ private fun ChooseFeeBottomSheetContent_Preview() { selectedFee = FeeType.NORMAL, onSelectFeeType = {}, feeItems = feeItems, + readMore = stringReference("Read more"), + readMoreUrl = "", + onReadMoreClick = {}, ), ) } @@ -141,6 +177,9 @@ private fun ChooseFeeBottomSheetContent_Preview() { selectedFee = FeeType.NORMAL, onSelectFeeType = {}, feeItems = feeItems, + readMore = stringReference("Read more"), + readMoreUrl = "", + onReadMoreClick = {}, ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 446b40e5ae..53e3148278 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -25,6 +25,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal import java.math.RoundingMode +import java.util.Locale import kotlin.math.min /** @@ -233,7 +234,10 @@ internal class StateBuilder( isBalanceHidden = isBalanceHiddenProvider(), ), receiveCardData = SwapCardState.SwapCardData( - type = TransactionCardType.ReadOnly(actions.onReceiveCardWarningClick), + type = TransactionCardType.ReadOnly( + showWarning = true, + actions.onReceiveCardWarningClick, + ), amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()), amountEquivalent = getFormattedFiatAmount(quoteModel.toTokenInfo.amountFiat), token = toCurrencyStatus, @@ -852,17 +856,30 @@ internal class StateBuilder( ) } - fun createImpactAlert( + fun createAlert( uiState: SwapStateHolder, + isPriceImpact: Boolean, + token: String, providerType: ExchangeProviderType, onAlertClick: () -> Unit, ): SwapStateHolder { val message = when (providerType) { - ExchangeProviderType.CEX -> resourceReference(R.string.express_cex_fee_explanation) - ExchangeProviderType.DEX -> resourceReference(R.string.swapping_high_price_impact_description) + ExchangeProviderType.CEX -> resourceReference(R.string.swapping_alert_cex_description, wrappedList(token)) + ExchangeProviderType.DEX -> { + val refs = buildList { + if (isPriceImpact) { + add(resourceReference(R.string.swapping_high_price_impact_description)) + add(stringReference("\n\n")) + } + add(resourceReference(R.string.swapping_alert_dex_description)) + } + + combinedReference(refs.toWrappedList()) + } } return uiState.copy( alert = SwapWarning.GenericWarning( + title = resourceReference(R.string.swapping_alert_title), message = message, onClick = onAlertClick, type = GenericWarningType.OTHER, @@ -1074,7 +1091,10 @@ internal class StateBuilder( } actions.onSelectFeeType.invoke(selectedItem) }, + readMoreUrl = buildReadMoreUrl(), feeItems = txFeeState.toFeeItemState(), + readMore = resourceReference(R.string.common_fee_selector_link_description), + onReadMoreClick = actions.onFeeReadMoreClick, ) return uiState.copy( bottomSheetConfig = TangemBottomSheetConfig( @@ -1085,6 +1105,14 @@ internal class StateBuilder( ) } + private fun buildReadMoreUrl(): String { + return buildString { + append(FEE_READ_MORE_URL_FIRST_PART) + append(getLocaleName()) + append(FEE_READ_MORE_URL_SECOND_PART) + } + } + fun updateSelectedFeeBottomSheet(uiState: SwapStateHolder, selectedFee: FeeType): SwapStateHolder { val config = uiState.bottomSheetConfig?.content as? ChooseFeeBottomSheetConfig return if (config != null) { @@ -1337,12 +1365,24 @@ internal class StateBuilder( return text.replace(",", ".").toBigDecimalOrNull() } + private fun getLocaleName(): String { + return if (Locale.getDefault().language == "ru") { + RU_LOCALE + } else { + EN_LOCALE + } + } + private companion object { + private const val RU_LOCALE = "ru" + private const val EN_LOCALE = "en" const val ADDRESS_MIN_LENGTH = 11 const val ADDRESS_FIRST_PART_LENGTH = 7 const val ADDRESS_SECOND_PART_LENGTH = 4 private const val PRICE_IMPACT_THRESHOLD = 0.1 private const val UNKNOWN_AMOUNT_SIGN = "—" private const val MAX_DECIMALS_TO_SHOW = 8 + private const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/" + private const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/" } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index f58b501863..4f2a3fefa1 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -111,8 +111,13 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi } else { state.alert.message?.resolveReference() ?: stringResource(id = R.string.common_unknown_error) } - SimpleOkDialog( + BasicDialog( + title = state.alert.title?.resolveReference(), message = message, + confirmButton = DialogButton( + title = stringResource(id = R.string.common_ok), + onClick = state.alert.onClick, + ), onDismissDialog = state.alert.onClick, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 038465b6d3..9e7a5f8b2d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -274,29 +274,49 @@ private fun Content( SpacerH8() if (amountEquivalent != null) { - if (type is TransactionCardType.ReadOnly && priceImpact !is PriceImpact.Empty) { + if (type is TransactionCardType.ReadOnly) { Row { - Text( - text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.getIntPercentValue()), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - modifier = Modifier - .defaultMinSize(minHeight = TangemTheme.dimens.size20) - .align(Alignment.CenterVertically), - ) - SpacerW4() - IconButton( - onClick = { - type.onWarningClick?.invoke() - }, - modifier = Modifier.size(size = TangemTheme.dimens.size20), - ) { - Icon( - painter = painterResource(id = R.drawable.ic_alert_24), - contentDescription = null, - tint = TangemTheme.colors.icon.attention, - modifier = Modifier.align(Alignment.CenterVertically), + if (priceImpact is PriceImpact.Value) { + Text( + text = makePriceImpactBalanceWarning( + amountEquivalent, + priceImpact.getIntPercentValue(), + ), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size20) + .align(Alignment.CenterVertically), ) + } else { + AnimatedContent(targetState = amountEquivalent, label = "") { + Text( + text = it, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20), + ) + } + } + if (type.showWarning) { + SpacerW4() + IconButton( + onClick = { + type.onWarningClick?.invoke() + }, + modifier = Modifier.size(size = TangemTheme.dimens.size20), + ) { + Icon( + painter = painterResource(id = R.drawable.ic_alert_24), + contentDescription = null, + tint = if (priceImpact is PriceImpact.Value) { + TangemTheme.colors.text.attention + } else { + TangemTheme.colors.text.tertiary + }, + modifier = Modifier.align(Alignment.CenterVertically), + ) + } } } } else { @@ -471,22 +491,52 @@ private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: In @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable -private fun Preview_SwapMainCard_InLightTheme() { +private fun Preview_TransactionCard_InLightTheme() { TangemTheme(isDark = false) { TransactionCardPreview() + } +} + +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +private fun Preview_TransactionCardWithPriceImpact_InLightTheme() { + TangemTheme(isDark = false) { TransactionCardPreviewWithPriceImpact() } } @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable -private fun Preview_SwapMainCard_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() { + TangemTheme(isDark = false) { + TransactionCardPreviewWithoutPriceImpact() + } +} + +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +private fun Preview_TransactionCard_InDarkTheme() { + TangemTheme(isDark = false) { TransactionCardPreview() + } +} + +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() { + TangemTheme(isDark = false) { TransactionCardPreviewWithPriceImpact() } } +@Preview(widthDp = 328, heightDp = 116, showBackground = true) +@Composable +private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() { + TangemTheme(isDark = false) { + TransactionCardPreviewWithoutPriceImpact() + } +} + @Composable private fun TransactionCardPreview() { TransactionCard( @@ -518,4 +568,20 @@ private fun TransactionCardPreviewWithPriceImpact() { ) } +@Composable +@Suppress("MagicNumber") +private fun TransactionCardPreviewWithoutPriceImpact() { + TransactionCard( + type = TransactionCardType.ReadOnly(), + amountEquivalent = "1 000 000", + tokenIconUrl = "", + tokenCurrency = "DAI", + networkIconRes = R.drawable.img_polygon_22, + onChangeTokenClick = {}, + balance = "123", + textFieldValue = TextFieldValue(), + priceImpact = PriceImpact.Empty(), + ) +} + // endregion preview \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index 7623970edf..aada4b7d2b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -18,7 +18,7 @@ data class SwapProcessDataState( val approveDataModel: RequestApproveStateData? = null, val approveType: ApproveType? = null, val swapDataModel: SwapDataModel? = null, - val selectedFee: TxFee? = null, // todo + val selectedFee: TxFee? = null, val tokensDataState: TokensDataStateExpress? = null, val selectedProvider: SwapProvider? = null, val lastLoadedSwapStates: Map = emptyMap(), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index ace4cb9006..d194494046 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -879,13 +879,20 @@ internal class SwapViewModel @Inject constructor( }, onReceiveCardWarningClick = { val selectedProvider = dataState.selectedProvider ?: return@UiActions - uiState = stateBuilder.createImpactAlert( + val currencySymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return@UiActions + val isPriceImpact = uiState.priceImpact is PriceImpact.Value + uiState = stateBuilder.createAlert( uiState = uiState, + isPriceImpact = isPriceImpact, + token = currencySymbol, providerType = selectedProvider.type, ) { uiState = stateBuilder.clearAlert(uiState) } }, + onFeeReadMoreClick = { + swapRouter.openUrl(it) + }, ) } diff --git a/features/tester/api/src/main/java/com/tangem/features/tester/api/TesterFeatureToggles.kt b/features/tester/api/src/main/java/com/tangem/features/tester/api/TesterFeatureToggles.kt deleted file mode 100644 index 84a02490cc..0000000000 --- a/features/tester/api/src/main/java/com/tangem/features/tester/api/TesterFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.tester.api - -/** - * Tester feature toggles - * -[REDACTED_AUTHOR] - */ -interface TesterFeatureToggles { - - val isDerivePublicKeysRefactoringEnabled: Boolean -} \ No newline at end of file diff --git a/features/tester/impl/build.gradle.kts b/features/tester/impl/build.gradle.kts index 73011f1a76..9bc0022b59 100644 --- a/features/tester/impl/build.gradle.kts +++ b/features/tester/impl/build.gradle.kts @@ -27,17 +27,18 @@ dependencies { implementation(projects.domain.appTheme) implementation(projects.domain.appTheme.models) - /** Core modules */ - implementation(project(":core:featuretoggles")) - implementation(project(":core:ui")) - - /** Feature Apis */ - implementation(project(":features:tester:api")) - - /** Other modules */ - implementation(project(":libs:crypto")) - /** Other libraries */ implementation(deps.arrow.core) implementation(deps.timber) + + /** Core modules */ + implementation(projects.core.featuretoggles) + implementation(projects.core.ui) + implementation(projects.core.utils) + + /** Feature Apis */ + implementation(projects.features.tester.api) + + /** Other modules */ + implementation(projects.libs.crypto) } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/di/TesterFeatureTogglesModule.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/di/TesterFeatureTogglesModule.kt deleted file mode 100644 index ee49bf39b7..0000000000 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/di/TesterFeatureTogglesModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.feature.tester.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.feature.tester.featuretoggles.DefaultTesterFeatureToggles -import com.tangem.features.tester.api.TesterFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object TesterFeatureTogglesModule { - - @Provides - @Singleton - fun provideTesterFeatureToggles(featureTogglesManager: FeatureTogglesManager): TesterFeatureToggles { - return DefaultTesterFeatureToggles(featureTogglesManager = featureTogglesManager) - } -} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/featuretoggles/DefaultTesterFeatureToggles.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/featuretoggles/DefaultTesterFeatureToggles.kt deleted file mode 100644 index 77e2de389a..0000000000 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/featuretoggles/DefaultTesterFeatureToggles.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.feature.tester.featuretoggles - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.tester.api.TesterFeatureToggles - -/** - * Default implementation of Tester feature toggles - * - * @property featureTogglesManager manager for getting information about the availability of feature toggles - * -[REDACTED_AUTHOR] - */ -internal class DefaultTesterFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : TesterFeatureToggles { - - override val isDerivePublicKeysRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "DERIVE_PUBLIC_KEYS_REFACTORING_ENABLED") -} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt index 4c10030524..037946a145 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt @@ -4,24 +4,29 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.core.featuretoggle.manager.MutableFeatureTogglesManager import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject /** * ViewModel for screen with list of feature toggles * * @property featureTogglesManager manager for getting information about the availability of feature toggles + * @property dispatchers coroutine dispatchers provider * [REDACTED_AUTHOR] */ @HiltViewModel internal class FeatureTogglesViewModel @Inject constructor( private val featureTogglesManager: FeatureTogglesManager, + private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { /** Current ui state */ @@ -47,9 +52,11 @@ internal class FeatureTogglesViewModel @Inject constructor( } private fun onToggleValueChange(name: String, isEnabled: Boolean) { - mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled) + viewModelScope.launch(dispatchers.main) { + mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled) - uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles()) + uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles()) + } } private fun MutableFeatureTogglesManager.getTesterFeatureToggles(): List { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 458e547586..77f27a4cac 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -1,10 +1,15 @@ package com.tangem.feature.tokendetails.presentation.tokendetails import androidx.compose.ui.graphics.Color +import androidx.paging.PagingData import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.components.marketprice.PriceChangeState +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton @@ -76,8 +81,8 @@ internal object TokenDetailsPreviewData { val balanceLoading = TokenDetailsBalanceBlockState.Loading(actionButtons = actionButtons) val balanceContent = TokenDetailsBalanceBlockState.Content( actionButtons = actionButtons, - fiatBalance = "123,00$", - cryptoBalance = "866,96 USDT", + fiatBalance = "91,50$", + cryptoBalance = "966,96 XLM", ) val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = actionButtons) @@ -88,7 +93,131 @@ internal object TokenDetailsPreviewData { onRefresh = {}, ) - val tokenDetailsState = TokenDetailsState( + private val txHistoryItems = listOf( + TxHistoryState.TxHistoryItemState.Title(onExploreClick = {}), + TxHistoryState.TxHistoryItemState.GroupTitle( + title = "Today", + itemKey = "Today", + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "1", + amount = "-0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.OUTGOING, + onClick = {}, + iconRes = R.drawable.ic_arrow_up_24, + title = stringReference(value = "Sending"), + subtitle = stringReference(value = "to: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "2", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_arrow_down_24, + title = stringReference(value = "Receiving"), + subtitle = stringReference(value = "from: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "3", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_doc_24, + title = stringReference(value = "Approving"), + subtitle = stringReference(value = "from: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "4", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_exchange_vertical_24, + title = stringReference(value = "Swapping"), + subtitle = stringReference(value = "contract: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.GroupTitle( + title = "Yesterday", + itemKey = "Yesterday", + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "5", + amount = "-0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.OUTGOING, + onClick = {}, + iconRes = R.drawable.ic_arrow_up_24, + title = stringReference(value = "Sending"), + subtitle = stringReference(value = "to: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "6", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_arrow_down_24, + title = stringReference(value = "Receiving"), + subtitle = stringReference(value = "from: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "7", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_doc_24, + title = stringReference(value = "Approving"), + subtitle = stringReference(value = "from: 33BddS...ga2B"), + timestamp = 0, + ), + ), + TxHistoryState.TxHistoryItemState.Transaction( + state = TransactionState.Content( + txHash = "8", + amount = "+0.500913 XLM", + time = "8:41", + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + onClick = {}, + iconRes = R.drawable.ic_exchange_vertical_24, + title = stringReference(value = "Swapping"), + subtitle = stringReference(value = "contract: 33BddS...ga2B"), + timestamp = 0, + ), + ), + ) + + val tokenDetailsState_1 = TokenDetailsState( topAppBarConfig = tokenDetailsTopAppBarConfig, tokenInfoBlockState = tokenInfoBlockState, tokenBalanceBlockState = balanceLoading, @@ -108,4 +237,41 @@ internal object TokenDetailsPreviewData { isMarketPriceAvailable = false, event = consumedEvent(), ) + + val tokenDetailsState_2 = TokenDetailsState( + topAppBarConfig = tokenDetailsTopAppBarConfig, + tokenInfoBlockState = tokenInfoBlockState.copy( + name = "Stellar", + ), + tokenBalanceBlockState = balanceContent, + marketPriceBlockState = MarketPriceBlockState.Content( + currencySymbol = "XLM", + price = "0.11$", + priceChangeConfig = PriceChangeState.Content( + valueInPercent = "5,16%", + type = PriceChangeType.UP, + ), + ), + notifications = persistentListOf(), + txHistoryState = TxHistoryState.NotSupported( + onExploreClick = {}, + pendingTransactions = persistentListOf(), + ), + dialogConfig = null, + pendingTxs = persistentListOf(), + swapTxs = persistentListOf(), + pullToRefreshConfig = pullToRefreshConfig, + bottomSheetConfig = null, + isBalanceHidden = false, + isMarketPriceAvailable = true, + event = consumedEvent(), + ) + + val tokenDetailsState_3 = tokenDetailsState_2.copy( + txHistoryState = TxHistoryState.Content( + contentItems = MutableStateFlow( + value = PagingData.from(txHistoryItems), + ), + ), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 142947657c..e8c09263eb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -12,7 +12,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification -import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsPendingTxToTransactionStateConverter +import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @@ -29,7 +29,7 @@ internal class TokenDetailsLoadedBalanceConverter( ) : Converter, TokenDetailsState> { private val txHistoryItemConverter by lazy { - TokenDetailsPendingTxToTransactionStateConverter(symbol, decimals, clickIntents) + TokenDetailsTxHistoryTransactionStateConverter(symbol, decimals, clickIntents) } override fun convert(value: Either): TokenDetailsState { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsPendingTxToTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsPendingTxToTransactionStateConverter.kt deleted file mode 100644 index 6ba156961c..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsPendingTxToTransactionStateConverter.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory - -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents -import com.tangem.features.tokendetails.impl.R -import com.tangem.utils.converter.Converter -import com.tangem.utils.toBriefAddressFormat -import com.tangem.utils.toFormattedCurrencyString -import org.joda.time.DateTime -import org.joda.time.DateTimeZone - -// FIXME: Refactoring needed -/** Same as [TokenDetailsTxHistoryTransactionStateConverter] but with other timestamp format */ -internal class TokenDetailsPendingTxToTransactionStateConverter( - private val symbol: String, - private val decimals: Int, - private val clickIntents: TokenDetailsClickIntents, -) : Converter { - - override fun convert(value: TxHistoryItem): TransactionState { - return createTransactionStateItem(item = value) - } - - private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { - return TransactionState.Content( - txHash = item.txHash, - amount = item.getAmount(), - timestamp = item.timestampInMillis.toTimeFormat(), - status = item.status.tiUiStatus(), - direction = item.extractDirection(), - iconRes = item.extractIcon(), - title = item.extractTitle(), - subtitle = item.extractSubtitle(), - onClick = { clickIntents.onTransactionClick(item.txHash) }, - ) - } - - private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) { - R.drawable.ic_close_24 - } else { - when (type) { - is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24 - is TxHistoryItem.TransactionType.Operation, - is TxHistoryItem.TransactionType.Swap, - is TxHistoryItem.TransactionType.Transfer, - is TxHistoryItem.TransactionType.UnknownOperation, - -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 - } - } - - private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) { - is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval) - is TxHistoryItem.TransactionType.Operation -> stringReference(type.name) - is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap) - is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) - } - - private fun TxHistoryItem.extractSubtitle(): TextReference = - when (val interactionAddress = interactionAddressType) { - is TxHistoryItem.InteractionAddressType.Contract -> resourceReference( - id = R.string.transaction_history_contract_address, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)), - ) - is TxHistoryItem.InteractionAddressType.User -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - } - - private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { - TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed - TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed - TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed - } - - private fun Long.toTimeFormat(): String { - return DateTimeFormatters.formatTime(time = DateTime(this, DateTimeZone.getDefault())) - } - - private fun TxHistoryItem.extractDirection() = - if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING - - private fun TxHistoryItem.getAmount(): String { - val prefix = when (status) { - TxHistoryItem.TransactionStatus.Failed -> "" - else -> if (isOutgoing) "-" else "+" - } - return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt index 56ae213609..9fb6df70b1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt @@ -5,7 +5,6 @@ import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState import com.tangem.core.ui.utils.toDateFormat -import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents @@ -52,8 +51,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter( terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE, item = TxHistoryItemState.Title(clickIntents::onExploreClick), ) - .insertGroupTitle() // method uses the raw timestamp - .formatTransactionsTimestamp() // method formats the timestamp + .insertGroupTitle() } } .launchIn(CoroutineScope(Dispatchers.IO)) @@ -94,28 +92,10 @@ internal class TokenDetailsTxHistoryItemFlowConverter( } } - /** - * Map the [PagingData] to format the [TxHistoryItemState] timestamp - */ - private fun PagingData.formatTransactionsTimestamp(): PagingData { - return map { txHistoryItemState -> - if (txHistoryItemState is TxHistoryItemState.Transaction && - txHistoryItemState.state is TransactionState.Content - ) { - val txContent = txHistoryItemState.state as TransactionState.Content - txHistoryItemState.copy( - state = txContent.copy(timestamp = txContent.timestamp.toLong().toTimeFormat()), - ) - } else { - txHistoryItemState - } - } - } - private fun TxHistoryItemState?.getTimestamp(): Long? { return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) { val txContent = this.state as TransactionState.Content - requireNotNull(txContent.timestamp.toLongOrNull()) { "Timestamp must be Long type" } + txContent.timestamp } else { null } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 22d68befc8..1e2ab2341d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R @@ -13,8 +14,6 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat import com.tangem.utils.toFormattedCurrencyString -// FIXME: Refactoring needed -/** Same as [TokenDetailsPendingTxToTransactionStateConverter] but with other timestamp format */ internal class TokenDetailsTxHistoryTransactionStateConverter( private val symbol: String, private val decimals: Int, @@ -30,12 +29,13 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( return TransactionState.Content( txHash = item.txHash, amount = item.getAmount(), - timestamp = item.getRawTimestamp(), + time = item.timestampInMillis.toTimeFormat(), status = item.status.tiUiStatus(), direction = item.extractDirection(), iconRes = item.extractIcon(), title = item.extractTitle(), subtitle = item.extractSubtitle(), + timestamp = item.timestampInMillis, onClick = { clickIntents.onTransactionClick(item.txHash) }, ) } @@ -87,14 +87,6 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( private fun TxHistoryItem.extractDirection() = if (isOutgoing) Direction.OUTGOING else Direction.INCOMING - /** - * Get timestamp without formatting. - * It's life hack that help us to add transaction's group title to flow. - * - * @see [convert] - */ - private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString() - private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 1f1ae20a89..070d2b0065 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -20,6 +21,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.paging.compose.collectAsLazyPagingItems import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig @@ -82,14 +85,12 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { .pullRefresh(pullRefreshState), ) { LazyColumn( - modifier = Modifier - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = TangemTheme.dimens.spacing16), ) { item { TokenInfoBlock( - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing4) - .padding(horizontal = horizontalPadding), + modifier = Modifier.padding(horizontal = horizontalPadding), state = state.tokenInfoBlockState, ) } @@ -181,18 +182,32 @@ internal fun TokenDetailsEventEffect(snackbarHostState: SnackbarHostState, event ) } -@Preview +// region Preview +@Preview(showBackground = true, widthDp = 360) @Composable -private fun Preview_TokenDetailsScreen_LightTheme() { - TangemTheme(isDark = false) { - TokenDetailsScreen(state = TokenDetailsPreviewData.tokenDetailsState) +private fun TokenDetailsScreenPreview_Light( + @PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState, +) { + TangemTheme { + TokenDetailsScreen(state) } } -@Preview +@Preview(showBackground = true, widthDp = 360) @Composable -private fun Preview_TokenDetailsScreen_DarkTheme() { +private fun TokenDetailsScreenPreview_Dark( + @PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState, +) { TangemTheme(isDark = true) { - TokenDetailsScreen(state = TokenDetailsPreviewData.tokenDetailsState) + TokenDetailsScreen(state) } -} \ No newline at end of file +} + +private class TokenDetailsScreenParameterProvider : CollectionPreviewParameterProvider( + collection = listOf( + TokenDetailsPreviewData.tokenDetailsState_1, + TokenDetailsPreviewData.tokenDetailsState_2, + TokenDetailsPreviewData.tokenDetailsState_3, + ), +) +// endregion Preview \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index f9911ae521..e8fd329b15 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -32,18 +33,21 @@ internal fun TokenDetailsBalanceBlock( color = TangemTheme.colors.background.primary, ) { Column { - Text( + Box( modifier = Modifier - .padding( - top = TangemTheme.dimens.spacing12, - start = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing12, - ), - text = stringResource(id = R.string.common_balance_title), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.subtitle2, - maxLines = 1, - ) + .padding(top = TangemTheme.dimens.spacing12) + .padding(horizontal = TangemTheme.dimens.spacing12) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.spacing24), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = stringResource(id = R.string.common_balance_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.subtitle2, + maxLines = 1, + ) + } FiatBalance( state = state, isBalanceHidden = isBalanceHidden, @@ -124,7 +128,7 @@ private fun CryptoBalance( } } -@Preview +@Preview(widthDp = 328, heightDp = 152) @Composable private fun Preview_TokenDetailsBalanceBlock_LightTheme( @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, @@ -134,7 +138,7 @@ private fun Preview_TokenDetailsBalanceBlock_LightTheme( } } -@Preview +@Preview(widthDp = 328, heightDp = 152) @Composable private fun Preview_TokenDetailsBalanceBlock_DarkTheme( @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 8b48d54da4..3254286ed2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -29,9 +28,15 @@ private const val NORMAL_ALPHA = 1f @Composable internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Modifier) { - Row(modifier = modifier.fillMaxWidth()) { + Row( + modifier = modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size60), + ) { Column( - modifier = Modifier.weight(1F), + modifier = Modifier + .align(Alignment.CenterVertically) + .weight(1F), ) { Text( text = state.name, @@ -88,7 +93,7 @@ private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) { ) Text( text = state.boldText, - style = TangemTheme.typography.caption2.copy(fontWeight = FontWeight.Medium), + style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.primary1, ) } diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt deleted file mode 100644 index f2bd26a6ba..0000000000 --- a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.wallet.featuretoggles - -/** - * Wallet feature toggles - * -[REDACTED_AUTHOR] - */ -interface WalletFeatureToggles { - - val isWalletsScrollingPreviewEnabled: Boolean -} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index a24c30a994..e2d5cb6d0f 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -73,10 +73,11 @@ dependencies { //TODO: Create api/impl modules for onboarding [REDACTED_JIRA] implementation(projects.features.onboarding) - implementation(projects.features.tester.api) /** Feature Apis */ implementation(projects.features.wallet.api) implementation(projects.features.tokendetails.api) implementation(projects.features.send.api) + implementation(projects.features.tester.api) + implementation(projects.features.manageTokens.api) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt deleted file mode 100644 index ed23c5d4c3..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.feature.wallet.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles -import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object WalletFeatureTogglesModule { - - @Provides - @Singleton - fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles { - return DefaultWalletFeatureToggles(featureTogglesManager = featureTogglesManager) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt index 81c4fd83ef..1e64fc4122 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt @@ -2,7 +2,6 @@ package com.tangem.feature.wallet.di import com.tangem.core.navigation.ReduxNavController import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter -import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.features.wallet.navigation.WalletRouter import dagger.Module import dagger.Provides @@ -16,10 +15,7 @@ internal object WalletRouterModule { @Provides @ActivityScoped - fun provideWalletRouter( - reduxNavController: ReduxNavController, - walletFeatureToggles: WalletFeatureToggles, - ): WalletRouter { - return DefaultWalletRouter(reduxNavController = reduxNavController, walletFeatureToggles = walletFeatureToggles) + fun provideWalletRouter(reduxNavController: ReduxNavController): WalletRouter { + return DefaultWalletRouter(reduxNavController = reduxNavController) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt deleted file mode 100644 index 628055d566..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.feature.wallet.featuretoggles - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles - -/** - * Default implementation of Wallet feature toggles - * - * @property featureTogglesManager manager for getting information about the availability of feature toggles - * -[REDACTED_AUTHOR] - */ -internal class DefaultWalletFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : WalletFeatureToggles { - - override val isWalletsScrollingPreviewEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "WALLETS_SCROLLING_PREVIEW_ENABLED") -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index e8c0881cfe..74df363767 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.wallet.navigation.WalletRouter import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -22,6 +23,9 @@ internal class WalletFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + internal lateinit var manageTokensUi: ManageTokensUi + /** Feature router */ @Inject internal lateinit var walletRouter: WalletRouter @@ -38,7 +42,10 @@ internal class WalletFragment : ComposeFragment() { setSystemBarsColor(systemBarsColor) } - _walletRouter.Initialize(onFinish = requireActivity()::finish) + _walletRouter.Initialize( + onFinish = requireActivity()::finish, + manageTokensUi = manageTokensUi, + ) } companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index be817ab65a..92abf8f92f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -1,17 +1,10 @@ package com.tangem.feature.wallet.presentation.common -import androidx.paging.PagingData import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.tokenicon.TokenIconState -import com.tangem.core.ui.components.marketprice.MarketPriceBlockState -import com.tangem.core.ui.components.marketprice.PriceChangeState import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.impl.R @@ -19,16 +12,9 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState -import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.components.* -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState -import kotlinx.collections.immutable.persistentListOf +import com.tangem.feature.wallet.presentation.wallet.state.model.* import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.flow.MutableStateFlow import java.util.UUID @Suppress("LargeClass") @@ -80,14 +66,6 @@ internal object WalletPreviewData { ) } - val walletListConfig by lazy { - WalletsListConfig( - selectedWalletIndex = 0, - wallets = wallets.values.toPersistentList(), - onWalletChange = {}, - ) - } - val coinIconState get() = TokenIconState.CoinIcon( url = null, @@ -320,144 +298,4 @@ internal object WalletPreviewData { ), ).toImmutableList(), ) - - private val manageButtons by lazy { - persistentListOf( - WalletManageButton.Buy(enabled = true, onClick = {}), - WalletManageButton.Send(enabled = true, onClick = {}), - WalletManageButton.Receive(enabled = true, onClick = {}), - WalletManageButton.Sell(enabled = true, onClick = {}), - WalletManageButton.Swap(enabled = true, onClick = {}), - ) - } - - val multicurrencyWalletScreenState by lazy { - WalletMultiCurrencyState.Content( - onBackClick = {}, - topBarConfig = topBarConfig, - walletsListConfig = walletListConfig, - tokensListState = WalletTokensListState.Content( - persistentListOf( - TokensListItemState.NetworkGroupTitle(id = 0, stringReference("Bitcoin")), - TokensListItemState.Token( - tokenItemVisibleState.copy( - id = "token_1", - titleState = TokenItemState.TitleState.Content(text = "Ethereum"), - cryptoAmountState = TokenItemState.CryptoAmountState.Content("1,89340821 ETH"), - ), - ), - TokensListItemState.Token( - tokenItemVisibleState.copy( - id = "token_2", - titleState = TokenItemState.TitleState.Content(text = "Ethereum"), - cryptoAmountState = TokenItemState.CryptoAmountState.Content("1,89340821 ETH"), - ), - ), - TokensListItemState.Token( - tokenItemVisibleState.copy( - id = "token_3", - titleState = TokenItemState.TitleState.Content(text = "Ethereum"), - cryptoAmountState = TokenItemState.CryptoAmountState.Content("1,89340821 ETH"), - ), - ), - TokensListItemState.Token( - tokenItemVisibleState.copy( - id = "token_4", - titleState = TokenItemState.TitleState.Content(text = "Ethereum"), - cryptoAmountState = TokenItemState.CryptoAmountState.Content("1,89340821 ETH"), - ), - ), - TokensListItemState.NetworkGroupTitle(id = 1, stringReference("Ethereum")), - TokensListItemState.Token( - tokenItemVisibleState.copy( - id = "token_5", - titleState = TokenItemState.TitleState.Content(text = "Ethereum"), - cryptoAmountState = TokenItemState.CryptoAmountState.Content("1,89340821 ETH"), - ), - ), - ), - organizeTokensButton = WalletTokensListState.OrganizeTokensButtonState.Visible(isEnabled = true, {}), - ), - pullToRefreshConfig = WalletPullToRefreshConfig( - isRefreshing = false, - onRefresh = {}, - ), - notifications = persistentListOf( - WalletNotification.Critical.DevCard, - WalletNotification.Informational.MissingAddresses(missingAddressesCount = 0, onGenerateClick = {}), - WalletNotification.Warning.NetworksUnreachable, - ), - bottomSheetConfig = bottomSheet, - onManageTokensClick = {}, - event = consumedEvent(), - isBalanceHidden = false, - ) - } - - val singleWalletScreenState by lazy { - WalletSingleCurrencyState.Content( - onBackClick = {}, - topBarConfig = topBarConfig, - walletsListConfig = walletListConfig, - pullToRefreshConfig = WalletPullToRefreshConfig( - isRefreshing = false, - onRefresh = {}, - ), - notifications = persistentListOf(WalletNotification.Warning.NetworksUnreachable), - buttons = manageButtons, - bottomSheetConfig = bottomSheet, - marketPriceBlockState = MarketPriceBlockState.Content( - currencySymbol = "BTC", - price = "98900.12$", - priceChangeConfig = PriceChangeState.Content( - valueInPercent = "5.16%", - type = PriceChangeType.UP, - ), - ), - txHistoryState = TxHistoryState.Content( - contentItems = MutableStateFlow( - PagingData.from( - listOf( - TxHistoryState.TxHistoryItemState.GroupTitle( - title = "Today", - itemKey = UUID.randomUUID().toString(), - ), - TxHistoryState.TxHistoryItemState.Transaction( - TransactionState.Content( - txHash = UUID.randomUUID().toString(), - amount = "-0.500913 BTC", - timestamp = "8:41", - status = TransactionState.Content.Status.Unconfirmed, - direction = TransactionState.Content.Direction.OUTGOING, - iconRes = com.tangem.core.ui.R.drawable.ic_arrow_up_24, - title = resourceReference(com.tangem.core.ui.R.string.common_transfer), - subtitle = TextReference.Str("33BddS...ga2B"), - onClick = {}, - ), - ), - TxHistoryState.TxHistoryItemState.GroupTitle( - title = "Yesterday", - itemKey = UUID.randomUUID().toString(), - ), - TxHistoryState.TxHistoryItemState.Transaction( - TransactionState.Content( - txHash = UUID.randomUUID().toString(), - amount = "-0.500913 BTC", - timestamp = "8:41", - status = TransactionState.Content.Status.Confirmed, - direction = TransactionState.Content.Direction.OUTGOING, - iconRes = com.tangem.core.ui.R.drawable.ic_arrow_up_24, - title = resourceReference(com.tangem.core.ui.R.string.common_transfer), - subtitle = TextReference.Str("33BddS...ga2B"), - onClick = {}, - ), - ), - ), - ), - ), - ), - event = consumedEvent(), - isBalanceHidden = false, - ) - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 9c6177c05e..0b20c2a43a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -126,7 +126,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier Layout(content = content, modifier = modifier) { measurables, constraints -> val layoutWidth = constraints.maxWidth - val horizontalPadding = with(density) { dimens.size14.roundToPx() } + val horizontalPadding = with(density) { dimens.size12.roundToPx() } val verticalPadding = with(density) { dimens.size16.roundToPx() } val layoutWidthWithoutPaddings = layoutWidth - 2 * horizontalPadding @@ -137,7 +137,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier /* * Title width take the whole REMAINING space. - * If FiatAmount took the whole free space, then Title will has min width. + * If FiatAmount took the whole free space, then Title will have min width. */ val title: Placeable @@ -149,7 +149,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier /* * PriceChange width take the whole REMAINING space. - * If CryptoAmount took the whole free space, then PriceChange will has min width. + * If CryptoAmount took the whole free space, then PriceChange will have min width. */ val priceChange: Placeable? diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index 81cfb1f7a8..294fe204cd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -103,7 +103,7 @@ private fun TokenList( ) val listContentPadding = PaddingValues( - top = TangemTheme.dimens.spacing12, + top = TangemTheme.dimens.spacing4, bottom = TangemTheme.dimens.spacing92, start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing16, @@ -245,7 +245,7 @@ private fun TopBar( Row( modifier = Modifier .fillMaxWidth() - .heightIn(min = TangemTheme.dimens.size56), + .padding(bottom = TangemTheme.dimens.spacing8), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index fa60d699bf..54ca84fe4d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -1,10 +1,11 @@ package com.tangem.feature.wallet.presentation.router +import android.annotation.SuppressLint import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel @@ -19,24 +20,21 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.ReduxNavController import com.tangem.core.navigation.StateDialog -import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.onboarding.navigation.OnboardingRouter import com.tangem.feature.wallet.presentation.WalletFragment import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen -import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreenV2 import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModelV2 +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlin.properties.Delegates /** Default implementation of wallet feature router */ internal class DefaultWalletRouter( private val reduxNavController: ReduxNavController, - private val walletFeatureToggles: WalletFeatureToggles, ) : InnerWalletRouter { private var navController: NavHostController by Delegates.notNull() @@ -45,7 +43,7 @@ internal class DefaultWalletRouter( override fun getEntryFragment(): Fragment = WalletFragment.create() @Composable - override fun Initialize(onFinish: () -> Unit) { + override fun Initialize(onFinish: () -> Unit, manageTokensUi: ManageTokensUi) { this.onFinish = onFinish NavHost( @@ -53,21 +51,23 @@ internal class DefaultWalletRouter( startDestination = WalletRoute.Wallet.route, ) { composable(WalletRoute.Wallet.route) { - if (walletFeatureToggles.isWalletsScrollingPreviewEnabled) { - val viewModel = hiltViewModel().apply { - setWalletRouter(router = this@DefaultWalletRouter) - subscribeToLifecycle(LocalLifecycleOwner.current) - } - - WalletScreenV2(state = viewModel.uiState.collectAsStateWithLifecycle().value) - } else { - val viewModel = hiltViewModel().apply { - router = this@DefaultWalletRouter - } - LocalLifecycleOwner.current.lifecycle.addObserver(viewModel) - - WalletScreen(state = viewModel.uiState) + val viewModel = hiltViewModel().apply { + setWalletRouter(router = this@DefaultWalletRouter) + subscribeToLifecycle(LocalLifecycleOwner.current) } + + var bottomSheetHeaderHeight by remember { mutableStateOf(0.dp) } + + WalletScreen( + state = viewModel.uiState.collectAsStateWithLifecycle().value, + bottomSheetHeaderHeightProvider = { bottomSheetHeaderHeight }, + bottomSheetContent = { + // Manage Tokens + manageTokensUi.Content( + onHeaderSizeChange = { bottomSheetHeaderHeight = it }, + ) + }, + ) } composable( @@ -89,6 +89,7 @@ internal class DefaultWalletRouter( } } + @SuppressLint("RestrictedApi") override fun popBackStack(screen: AppScreen?) { /* * It's hack that avoid issue with closing the wallet screen. @@ -128,16 +129,19 @@ internal class DefaultWalletRouter( reduxNavController.navigate(action = NavigationAction.OpenUrl(url)) } - override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) { - reduxNavController.navigate( - action = NavigationAction.NavigateTo( - screen = AppScreen.WalletDetails, - bundle = bundleOf( - TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue, - TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency, + override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) { + val networkAddress = currencyStatus.value.networkAddress + if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) { + reduxNavController.navigate( + action = NavigationAction.NavigateTo( + screen = AppScreen.WalletDetails, + bundle = bundleOf( + TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue, + TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currencyStatus.currency, + ), ), - ), - ) + ) + } } override fun openStoriesScreen() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index c8f7b955ce..9cfd2fe858 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -3,8 +3,9 @@ package com.tangem.feature.wallet.presentation.router import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import com.tangem.core.navigation.AppScreen -import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.wallet.navigation.WalletRouter /** @@ -25,7 +26,7 @@ internal interface InnerWalletRouter : WalletRouter { */ @Suppress("TopLevelComposableFunctions") @Composable - fun Initialize(onFinish: () -> Unit) + fun Initialize(onFinish: () -> Unit, manageTokensUi: ManageTokensUi) /** Pop back stack */ fun popBackStack(screen: AppScreen? = null) @@ -43,7 +44,7 @@ internal interface InnerWalletRouter : WalletRouter { fun openUrl(url: String) /** Open token details screen */ - fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) + fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) /** Open stories screen */ fun openStoriesScreen() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 8ddb6b3b90..563ab70f1e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -11,7 +11,7 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import dagger.hilt.android.scopes.ViewModelScoped import java.math.BigDecimal diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index d8f8fa7457..899dd5a09a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -3,8 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 281f54a762..7de96e0c75 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -16,8 +16,8 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.repository.PromoRepository import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -40,7 +40,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private var readyForRateAppNotification = false - fun create(clickIntents: WalletClickIntentsV2): Flow> { + fun create(clickIntents: WalletClickIntents): Flow> { val userWallet = getSelectedWalletSyncUseCase().fold( ifLeft = { Timber.e("Failed to get selected wallet $it") @@ -78,7 +78,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun MutableList.addSwapPromoNotification( shouldShowPromo: Boolean, promoBanner: PromoBanner?, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { promoBanner ?: return val promoNotification = WalletNotification.SwapPromo( @@ -114,7 +114,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun MutableList.addInformationalNotifications( cardTypesResolver: CardTypesResolver, maybeTokenList: Either, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { addIf( element = WalletNotification.Informational.DemoCard, @@ -126,7 +126,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun MutableList.addMissingAddressesNotification( maybeTokenList: Either, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { val currencies = maybeTokenList.getMissingAddressCurrencies() @@ -162,7 +162,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( cardTypesResolver: CardTypesResolver, tokenList: Either, isNeedToBackup: Boolean, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { addIf( element = WalletNotification.Warning.MissingBackup( @@ -199,7 +199,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun MutableList.addRateTheAppNotification( isReadyToShowRating: Boolean, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { addIf( element = WalletNotification.RateApp( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt index a248e07dbe..edcbb820d8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt @@ -11,8 +11,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -33,7 +33,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( private var readyForRateAppNotification = false - fun create(clickIntents: WalletClickIntentsV2): Flow> { + fun create(clickIntents: WalletClickIntents): Flow> { val userWallet = getSelectedWalletSyncUseCase().fold( ifLeft = { Timber.e("Failed to get selected wallet $it") @@ -99,7 +99,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( cardTypesResolver: CardTypesResolver, maybePrimaryCurrencyStatus: Either, isNeedToBackup: Boolean, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { val cryptoCurrencyStatus = maybePrimaryCurrencyStatus.fold(ifLeft = { null }, ifRight = { it }) @@ -157,7 +157,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( private fun MutableList.addRateTheAppNotification( isReadyToShowRating: Boolean, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, ) { addIf( element = WalletNotification.RateApp( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt index 40ba99fbde..1f34e460f2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt @@ -8,7 +8,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletAdditionalInfo +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo import java.math.BigDecimal /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt index 3426a96ae1..bec1af9947 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt @@ -1,9 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain -import arrow.core.Either import com.tangem.common.extensions.isZero import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase -import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList @@ -13,9 +11,7 @@ internal class WalletWithFundsChecker @Inject constructor( private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, ) { - suspend fun check(maybeTokenList: Either) { - val tokenList = (maybeTokenList as? Either.Right)?.value ?: return - + suspend fun check(tokenList: TokenList) { val hasNonZeroWallets = when (tokenList) { is TokenList.GroupedByNetwork -> { tokenList.groups diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt index f897350e0e..cbb2dad87f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletContentLoaderFactory.kt @@ -3,7 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -17,7 +17,7 @@ internal class WalletContentLoaderFactory @Inject constructor( fun create( userWallet: UserWallet, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, isRefresh: Boolean = false, ): WalletContentLoader? { return when { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt index 4742aea08d..66990ac417 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/WalletScreenContentLoader.kt @@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope @@ -35,7 +35,7 @@ internal class WalletScreenContentLoader @Inject constructor( */ fun load( userWallet: UserWallet, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, isRefresh: Boolean = false, coroutineScope: CoroutineScope, ) { @@ -67,7 +67,7 @@ internal class WalletScreenContentLoader @Inject constructor( private fun loadInternal( userWallet: UserWallet, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, coroutineScope: CoroutineScope, isRefresh: Boolean, ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 4fba2e23e0..b12083df95 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -2,29 +2,31 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletConnectNetworksSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @Suppress("LongParameterList") internal class MultiWalletContentLoader( private val userWallet: UserWallet, private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getTokenListUseCase: GetTokenListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val reduxStateHolder: ReduxStateHolder, ) : WalletContentLoader(id = userWallet.walletId) { @@ -39,6 +41,7 @@ internal class MultiWalletContentLoader( walletWithFundsChecker = walletWithFundsChecker, getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + applyTokenListSortingUseCase = applyTokenListSortingUseCase, ), MultiWalletWarningsSubscriber( userWalletId = userWallet.walletId, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 59ed8ae50f..794574309a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -2,14 +2,15 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -22,11 +23,12 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val getTokenListUseCase: GetTokenListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val reduxStateHolder: ReduxStateHolder, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { - fun create(userWallet: UserWallet, clickIntents: WalletClickIntentsV2): WalletContentLoader { + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { return MultiWalletContentLoader( userWallet = userWallet, clickIntents = clickIntents, @@ -38,6 +40,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, reduxStateHolder = reduxStateHolder, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + applyTokenListSortingUseCase = applyTokenListSortingUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index e9ed0d42aa..45b3b831a5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -10,14 +10,14 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @Suppress("LongParameterList") internal class SingleWalletContentLoader( private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val isRefresh: Boolean, private val stateHolder: WalletStateController, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt index 1dce97e173..c6be994278 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt @@ -10,8 +10,8 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -30,7 +30,7 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { - fun create(userWallet: UserWallet, clickIntents: WalletClickIntentsV2, isRefresh: Boolean): WalletContentLoader { + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader { return SingleWalletContentLoader( userWallet = userWallet, clickIntents = clickIntents, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 8e436284e8..42e26f47f2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -7,16 +7,16 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAn import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenListSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @Suppress("LongParameterList") internal class SingleWalletWithTokenContentLoader( private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val stateHolder: WalletStateController, private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 6afb0a59bf..e92241ad9a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -7,8 +7,8 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAn import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import javax.inject.Inject // TODO: Refactor @@ -23,7 +23,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { - fun create(userWallet: UserWallet, clickIntents: WalletClickIntentsV2): SingleWalletWithTokenContentLoader { + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { return SingleWalletWithTokenContentLoader( userWallet = userWallet, clickIntents = clickIntents, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt index 7d41dcb257..63c0590221 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoader.kt @@ -1,46 +1,32 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.visa.GetVisaCurrencyUseCase +import com.tangem.domain.visa.GetVisaTxHistoryUseCase import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.subscribers.TxHistorySubscriber -import com.tangem.feature.wallet.presentation.wallet.subscribers.VisaWalletBalancesAndLimitsSubscriber +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.subscribers.VisaWalletSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -@Suppress("LongParameterList") internal class VisaWalletContentLoader( private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val isRefresh: Boolean, - private val stateHolder: WalletStateController, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, - private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, - private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, + private val stateController: WalletStateController, + private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase, private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { return listOf( - VisaWalletBalancesAndLimitsSubscriber( + VisaWalletSubscriber( userWallet = userWallet, - stateHolder = stateHolder, + stateController = stateController, isRefresh = isRefresh, getVisaCurrencyUseCase = getVisaCurrencyUseCase, + getVisaTxHistoryUseCase = getVisaTxHistoryUseCase, clickIntents = clickIntents, ), - TxHistorySubscriber( - userWallet = userWallet, - isRefresh = isRefresh, - stateHolder = stateHolder, - clickIntents = clickIntents, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, - txHistoryItemsCountUseCase = txHistoryItemsCountUseCase, - txHistoryItemsUseCase = txHistoryItemsUseCase, - ), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt index e81f30afff..ff7033b81d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/VisaWalletContentLoaderFactory.kt @@ -1,35 +1,28 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.visa.GetVisaCurrencyUseCase +import com.tangem.domain.visa.GetVisaTxHistoryUseCase import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @ViewModelScoped -@Suppress("LongParameterList") internal class VisaWalletContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, - private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, - private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, + private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase, ) { - fun create(userWallet: UserWallet, clickIntents: WalletClickIntentsV2, isRefresh: Boolean): WalletContentLoader { + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader { return VisaWalletContentLoader( userWallet = userWallet, clickIntents = clickIntents, isRefresh = isRefresh, - stateHolder = stateHolder, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, - txHistoryItemsCountUseCase = txHistoryItemsCountUseCase, - txHistoryItemsUseCase = txHistoryItemsUseCase, + stateController = stateHolder, getVisaCurrencyUseCase = getVisaCurrencyUseCase, + getVisaTxHistoryUseCase = getVisaTxHistoryUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt deleted file mode 100644 index dd3bbbeee7..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -/** - * Locked wallet state - * -[REDACTED_AUTHOR] - */ -internal sealed interface WalletLockedState { - - /** Lambda be invoked when unlock wallet notification is clicked */ - val onUnlockWalletsNotificationClick: () -> Unit - - /** Lambda be invoked when unlock wallet button is clicked */ - val onUnlockClick: () -> Unit - - /** Lambda be invoked when scan button is clicked */ - val onScanClick: () -> Unit - - /** Bottom sheet visibility */ - val isBottomSheetShow: Boolean - - /** Lambda be invoked when bottom sheet is dismissed */ - val onBottomSheetDismiss: () -> Unit - - /** Get selected wallet index */ - fun getSelectedWalletIndex(): Int { - return when (this) { - is WalletMultiCurrencyState.Locked -> walletsListConfig.selectedWalletIndex - is WalletSingleCurrencyState.Locked -> walletsListConfig.selectedWalletIndex - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt deleted file mode 100644 index 109b58c2b2..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMultiCurrencyState.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.event.consumedEvent -import com.tangem.feature.wallet.presentation.wallet.state.components.* -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -/** - * Multi currency wallet state - * -[REDACTED_AUTHOR] - */ -internal sealed class WalletMultiCurrencyState : WalletState.ContentState() { - - /** Tokens list state */ - abstract val tokensListState: WalletTokensListState - - data class Content( - override val onBackClick: () -> Unit, - override val topBarConfig: WalletTopBarConfig, - override val walletsListConfig: WalletsListConfig, - override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val notifications: ImmutableList, - override val bottomSheetConfig: TangemBottomSheetConfig?, - override val tokensListState: WalletTokensListState, - override val event: StateEvent = consumedEvent(), - override val isBalanceHidden: Boolean, - val isManageTokensAvailable: Boolean = true, - val onManageTokensClick: () -> Unit, - ) : WalletMultiCurrencyState() - - data class Locked( - override val onBackClick: () -> Unit, - override val topBarConfig: WalletTopBarConfig, - override val walletsListConfig: WalletsListConfig, - override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val onUnlockWalletsNotificationClick: () -> Unit, - override val onUnlockClick: () -> Unit, - override val onScanClick: () -> Unit, - override val isBottomSheetShow: Boolean = false, - override val onBottomSheetDismiss: () -> Unit = {}, - override val event: StateEvent = consumedEvent(), - override val isBalanceHidden: Boolean, - ) : WalletMultiCurrencyState(), WalletLockedState { - - override val notifications = persistentListOf( - WalletNotification.UnlockWallets(onUnlockWalletsNotificationClick), - ) - - override val bottomSheetConfig = TangemBottomSheetConfig( - isShow = isBottomSheetShow, - onDismissRequest = onBottomSheetDismiss, - content = WalletBottomSheetConfig.UnlockWallets( - onUnlockClick = onUnlockClick, - onScanClick = onScanClick, - ), - ) - - override val tokensListState: WalletTokensListState = WalletTokensListState.Locked - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt deleted file mode 100644 index 4bb761d008..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletSingleCurrencyState.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -import androidx.compose.runtime.Immutable -import androidx.paging.PagingData -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.marketprice.MarketPriceBlockState -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.event.consumedEvent -import com.tangem.feature.wallet.presentation.wallet.state.components.* -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.flow.MutableStateFlow - -/** - * Single currency wallet content state - * -[REDACTED_AUTHOR] - */ -@Immutable -internal sealed class WalletSingleCurrencyState : WalletState.ContentState() { - - /** Manage buttons */ - abstract val buttons: PersistentList - - /** Transactions history state */ - abstract val txHistoryState: TxHistoryState - - data class Content( - override val onBackClick: () -> Unit, - override val topBarConfig: WalletTopBarConfig, - override val walletsListConfig: WalletsListConfig, - override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val notifications: ImmutableList, - override val bottomSheetConfig: TangemBottomSheetConfig?, - override val buttons: PersistentList, - override val txHistoryState: TxHistoryState, - override val event: StateEvent = consumedEvent(), - override val isBalanceHidden: Boolean, - val marketPriceBlockState: MarketPriceBlockState, - ) : WalletSingleCurrencyState() - - data class Locked( - override val onBackClick: () -> Unit, - override val topBarConfig: WalletTopBarConfig, - override val walletsListConfig: WalletsListConfig, - override val pullToRefreshConfig: WalletPullToRefreshConfig, - override val buttons: PersistentList, - override val onUnlockWalletsNotificationClick: () -> Unit, - override val onUnlockClick: () -> Unit, - override val onScanClick: () -> Unit, - override val isBottomSheetShow: Boolean = false, - override val onBottomSheetDismiss: () -> Unit = {}, - override val event: StateEvent = consumedEvent(), - override val isBalanceHidden: Boolean, - val onExploreClick: () -> Unit, - ) : WalletSingleCurrencyState(), WalletLockedState { - - override val notifications = persistentListOf( - WalletNotification.UnlockWallets(onUnlockWalletsNotificationClick), - ) - - override val bottomSheetConfig = TangemBottomSheetConfig( - isShow = isBottomSheetShow, - onDismissRequest = onBottomSheetDismiss, - content = WalletBottomSheetConfig.UnlockWallets( - onUnlockClick = onUnlockClick, - onScanClick = onScanClick, - ), - ) - - override val txHistoryState: TxHistoryState = TxHistoryState.Content( - contentItems = MutableStateFlow( - value = PagingData.from( - data = listOf( - TxHistoryState.TxHistoryItemState.Title(onExploreClick = onExploreClick), - TxHistoryState.TxHistoryItemState.Transaction( - state = TransactionState.Locked(txHash = LOCKED_TX_HASH), - ), - ), - ), - ), - ) - - private companion object { - const val LOCKED_TX_HASH = "LOCKED_TX_HASH" - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt deleted file mode 100644 index a3e9904881..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletState.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.event.StateEvent -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import kotlinx.collections.immutable.ImmutableList - -/** - * Wallet screen state - * -[REDACTED_AUTHOR] - */ -internal sealed class WalletState { - - /** Lambda be invoked when back button is clicked */ - abstract val onBackClick: () -> Unit - - /** Wallet screen content state */ - sealed class ContentState : WalletState() { - - /** Top bar config */ - abstract val topBarConfig: WalletTopBarConfig - - /** Wallets list config */ - abstract val walletsListConfig: WalletsListConfig - - /** Pull to refresh config */ - abstract val pullToRefreshConfig: WalletPullToRefreshConfig - - /** Notifications */ - abstract val notifications: ImmutableList - - /** Bottom sheet config */ - abstract val bottomSheetConfig: TangemBottomSheetConfig? - - /** State event */ - abstract val event: StateEvent - - /** Whether balance should be hidden */ - abstract val isBalanceHidden: Boolean - - /** - * Util function that allow to make a copy - * - * @param walletsListConfig wallets list config - * @param pullToRefreshConfig pull to refresh config - * @param event state event - */ - fun copySealed( - walletsListConfig: WalletsListConfig = this.walletsListConfig, - pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig, - event: StateEvent = this.event, - isBalanceHidden: Boolean = this.isBalanceHidden, - ): ContentState { - return when (this) { - is WalletMultiCurrencyState.Content -> { - copy( - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig, - event = event, - isBalanceHidden = isBalanceHidden, - ) - } - is WalletMultiCurrencyState.Locked -> { - copy( - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig, - event = event, - isBalanceHidden = isBalanceHidden, - ) - } - is WalletSingleCurrencyState.Content -> { - copy( - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig, - event = event, - isBalanceHidden = isBalanceHidden, - ) - } - is WalletSingleCurrencyState.Locked -> { - copy( - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig, - event = event, - isBalanceHidden = isBalanceHidden, - ) - } - } - } - } - - /** - * Initial state - * - * @property onBackClick lambda be invoked when back button is clicked - */ - data class Initial(override val onBackClick: () -> Unit) : WalletState() -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/WalletStateController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt similarity index 65% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/WalletStateController.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt index d2e9c06cb8..f9e1705b63 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/WalletStateController.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt @@ -1,15 +1,16 @@ -package com.tangem.feature.wallet.presentation.wallet.state2 +package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.event.consumedEvent import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.NOT_INITIALIZED_WALLET_INDEX -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.OpenBottomSheetTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.WalletScreenStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig +import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.WalletScreenStateTransformer +import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -23,9 +24,12 @@ import javax.inject.Singleton [REDACTED_AUTHOR] */ @Singleton -internal class WalletStateController @Inject constructor() { +internal class WalletStateController @Inject constructor( + private val manageTokensFeatureToggles: ManageTokensFeatureToggles, +) { val uiState: StateFlow get() = mutableUiState + val value: WalletScreenState get() = uiState.value private val mutableUiState: MutableStateFlow = MutableStateFlow(value = getInitialState()) @@ -42,7 +46,11 @@ internal class WalletStateController @Inject constructor() { mutableUiState.update { getInitialState() } } - fun getWalletIfSelected(walletId: UserWalletId): WalletState? { + fun getWalletState(userWalletId: UserWalletId): WalletState? { + return value.wallets.firstOrNull { it.walletCardState.id == userWalletId } + } + + fun getWalletStateIfSelected(walletId: UserWalletId): WalletState? { val selectedWalletId = getSelectedWalletId() return value.wallets.firstOrNull { @@ -77,6 +85,7 @@ internal class WalletStateController @Inject constructor() { onWalletChange = {}, event = consumedEvent(), isHidingMode = false, + manageTokenRedesignToggle = manageTokensFeatureToggles.isRedesignedScreenEnabled, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt deleted file mode 100644 index fe750ba585..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components - -import com.tangem.core.ui.extensions.TextReference -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import javax.annotation.concurrent.Immutable - -/** - * Wallet tokens list state - * -[REDACTED_AUTHOR] - */ -internal sealed class WalletTokensListState { - - /** Empty token list state */ - object Empty : WalletTokensListState() - - /** - * Wallet content token list state - * - * @property items content items - */ - sealed class ContentState( - open val items: ImmutableList, - open val organizeTokensButton: OrganizeTokensButtonState, - ) : WalletTokensListState() - - /** - * Loading content state - * - * @property items content items - */ - data class Loading( - override val items: ImmutableList = persistentListOf(), - ) : ContentState(items = items, organizeTokensButton = OrganizeTokensButtonState.Hidden) - - /** - * Content state - * - * @property items content items - * @property organizeTokensButton represents the state of the 'Organize Tokens' button - */ - data class Content( - override val items: ImmutableList, - override val organizeTokensButton: OrganizeTokensButtonState, - ) : ContentState(items, organizeTokensButton) - - /** Locked content state */ - object Locked : ContentState( - items = persistentListOf( - TokensListItemState.NetworkGroupTitle(id = 42, name = TextReference.Res(id = R.string.main_tokens)), - TokensListItemState.Token(state = TokenItemState.Locked(id = LOCKED_TOKEN_ID)), - ), - organizeTokensButton = OrganizeTokensButtonState.Hidden, - ) - - /** - * Represents the state of the 'Organize Tokens' button. - */ - @Immutable - sealed class OrganizeTokensButtonState { - - /** Represents the state where the 'Organize Tokens' button is hidden. */ - object Hidden : OrganizeTokensButtonState() - - /** - * Represents the state where the 'Organize Tokens' button is visible. - * - * @property isEnabled Indicates if the button is enabled or not. - * @property onClick Callback to be executed when the button is clicked. - */ - data class Visible( - val isEnabled: Boolean, - val onClick: () -> Unit, - ) : OrganizeTokensButtonState() - } - - /** Tokens list item state */ - @Immutable - sealed interface TokensListItemState { - - val id: Any - - /** - * Network group title item - * - * @property name network name - */ - data class NetworkGroupTitle( - override val id: Int, - val name: TextReference, - ) : TokensListItemState - - /** - * Token item - * - * @property state token item state - */ - data class Token(val state: TokenItemState) : TokensListItemState { - override val id: String = state.id - } - } - - private companion object { - const val LOCKED_TOKEN_ID = "Locked#1" - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletsListConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletsListConfig.kt deleted file mode 100644 index 20eac2dec5..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletsListConfig.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components - -import kotlinx.collections.immutable.ImmutableList - -/** - * Wallets list config - * - * @property selectedWalletIndex selected wallet index - * @property wallets wallets list - * @property onWalletChange lambda be invoked when wallet is swiped - * -[REDACTED_AUTHOR] - */ -internal data class WalletsListConfig( - val selectedWalletIndex: Int, - val wallets: ImmutableList, - val onWalletChange: (Int) -> Unit, -) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt deleted file mode 100644 index 652a516847..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.TokenActionsState -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.isNullOrZero -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -/** - * Converter from loaded [TokenItemState.Content] to ImmutableList<[TokenActionButtonConfig]> - * - * @property currentWalletProvider current wallet provider - * @property clickIntents screen click intents - */ -internal class TokenActionsProvider( - private val currentWalletProvider: Provider, - private val clickIntents: WalletClickIntents, -) { - - fun provideActions(tokenActions: TokenActionsState): ImmutableList { - return tokenActions.states - .filterIfSingleWithToken() - .mapNotNull { - mapTokenActionState( - actionsState = it, - cryptoCurrencyStatus = tokenActions.cryptoCurrencyStatus, - ) - } - .toImmutableList() - } - - private fun List.filterIfSingleWithToken(): List { - return if (currentWalletProvider().scanResponse.cardTypesResolver.isSingleWalletWithToken()) { - filter { it !is TokenActionsState.ActionState.HideToken } - } else { - this - } - } - - private fun mapTokenActionState( - actionsState: TokenActionsState.ActionState, - cryptoCurrencyStatus: CryptoCurrencyStatus, - ): TokenActionButtonConfig? { - if (actionsState is TokenActionsState.ActionState.Send && cryptoCurrencyStatus.value.amount.isNullOrZero()) { - return null - } - val title: TextReference - val icon: Int - val action: () -> Unit - when (actionsState) { - is TokenActionsState.ActionState.Buy -> { - title = resourceReference(R.string.common_buy) - icon = R.drawable.ic_plus_24 - action = { clickIntents.onBuyClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.Receive -> { - title = resourceReference(R.string.common_receive) - icon = R.drawable.ic_arrow_down_24 - action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.Sell -> { - title = resourceReference(R.string.common_sell) - icon = R.drawable.ic_currency_24 - action = { clickIntents.onSellClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.Send -> { - title = resourceReference(R.string.common_send) - icon = R.drawable.ic_arrow_up_24 - action = { clickIntents.onMultiCurrencySendClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.Swap -> { - title = resourceReference(R.string.swapping_swap_action) - icon = R.drawable.ic_exchange_horizontal_24 - action = { clickIntents.onSwapClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.CopyAddress -> { - title = resourceReference(R.string.common_copy_address) - icon = R.drawable.ic_copy_24 - action = { clickIntents.onCopyAddressClick(cryptoCurrencyStatus) } - } - is TokenActionsState.ActionState.HideToken -> { - title = resourceReference(R.string.token_details_hide_token) - icon = R.drawable.ic_hide_24 - action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) } - } - } - return TokenActionButtonConfig( - text = title, - iconResId = icon, - onClick = action, - isWarning = actionsState is TokenActionsState.ActionState.HideToken, - enabled = actionsState.enabled, - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenListWithWallet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenListWithWallet.kt deleted file mode 100644 index bd9060c2ce..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenListWithWallet.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.wallets.models.UserWallet - -data class TokenListWithWallet( - val tokenList: TokenList, - val wallet: UserWallet, -) \ No newline at end of file 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 deleted file mode 100644 index ef2281406c..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.model.TokenActionsState -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList - -internal class WalletCryptoCurrencyActionsConverter( - private val currentWalletProvider: Provider, - private val currentStateProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: TokenActionsState): WalletState { - return when (val state = currentStateProvider()) { - is WalletSingleCurrencyState.Content -> state.copy(buttons = value.mapToManageButtons()) - is WalletSingleCurrencyState.Locked, - is WalletMultiCurrencyState, - is WalletState.Initial, - -> state - } - } - - private fun TokenActionsState.mapToManageButtons(): PersistentList { - return this.states - .filterIfS2C() - .mapNotNull { action -> - when (action) { - is TokenActionsState.ActionState.Buy -> { - WalletManageButton.Buy( - enabled = action.enabled, - onClick = { clickIntents.onBuyClick(cryptoCurrencyStatus) }, - ) - } - is TokenActionsState.ActionState.Receive -> { - WalletManageButton.Receive( - enabled = action.enabled, - onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }, - ) - } - is TokenActionsState.ActionState.Sell -> { - WalletManageButton.Sell( - enabled = action.enabled, - onClick = { clickIntents.onSellClick(cryptoCurrencyStatus) }, - ) - } - is TokenActionsState.ActionState.Send -> { - WalletManageButton.Send( - enabled = action.enabled, - onClick = { clickIntents.onSingleCurrencySendClick(cryptoCurrencyStatus) }, - ) - } - else -> { - null - } - } - } - .toPersistentList() - } - - private fun List.filterIfS2C(): List { - return if (currentWalletProvider().scanResponse.cardTypesResolver.isStart2Coin()) { - filterNot { it is TokenActionsState.ActionState.Buy || it is TokenActionsState.ActionState.Sell } - } else { - this - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletDeleteStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletDeleteStateConverter.kt deleted file mode 100644 index 94b4b07979..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletDeleteStateConverter.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletDeleteStateConverter.DeleteWalletModel -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -/** - * Converter that responds on wallet deleting action. Returns [WalletState] without deleted wallet. - * - * @property currentStateProvider current state provider - */ -internal class WalletDeleteStateConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: DeleteWalletModel): WalletState { - return when (val state = currentStateProvider()) { - is WalletState.ContentState -> { - value.cacheState.copySealed( - walletsListConfig = state.walletsListConfig.copy( - selectedWalletIndex = value.action.selectedWalletIndex, - wallets = state.walletsListConfig.wallets.deleteWallet(id = value.action.deletedWalletId), - ), - pullToRefreshConfig = value.cacheState.pullToRefreshConfig.copy(isRefreshing = false), - ) - } - is WalletState.Initial -> state - } - } - - private fun List.deleteWallet(id: UserWalletId): ImmutableList { - return this - .mapIndexedNotNull { index, currentWallet -> - if (currentWallet.id == id) return@mapIndexedNotNull null - getOrNull(index) ?: return@mapIndexedNotNull null - } - .toImmutableList() - } - - data class DeleteWalletModel( - val cacheState: WalletState.ContentState, - val action: WalletsUpdateActionResolver.Action.DeleteWallet, - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt deleted file mode 100644 index 1a1b95cedc..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import arrow.core.Either -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorConverter -import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -/** - * Converter from loaded [TokenListError] or [TokenList] to [WalletMultiCurrencyState] - * - * @property currentStateProvider current ui state provider - * @property tokenListErrorConverter converter of tokens list - * @param appCurrencyProvider app currency provider - * @param currentWalletProvider current wallet provider - * @param clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -@Suppress("LongParameterList") -internal class WalletLoadedTokensListConverter( - private val currentStateProvider: Provider, - private val tokenListErrorConverter: TokenListErrorConverter, - appCurrencyProvider: Provider, - currentWalletProvider: Provider, - clickIntents: WalletClickIntents, -) : Converter, WalletState> { - - private val tokenListStateConverter = TokenListToWalletStateConverter( - currentStateProvider = currentStateProvider, - currentWalletProvider = currentWalletProvider, - appCurrencyProvider = appCurrencyProvider, - clickIntents = clickIntents, - ) - - override fun convert(value: Either): WalletState { - return value.fold( - ifLeft = tokenListErrorConverter::convert, - ifRight = tokenListStateConverter::convert, - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt deleted file mode 100644 index ac45532c00..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList - -internal class WalletLockedConverter( - private val currentStateProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: Unit): WalletState { - return when (val state = currentStateProvider()) { - is WalletMultiCurrencyState.Content -> state.toMultiCurrencyLockedState() - is WalletSingleCurrencyState.Content -> state.toSingleCurrencyLockedState() - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> state - } - } - - private fun WalletMultiCurrencyState.Content.toMultiCurrencyLockedState(): WalletState { - return WalletMultiCurrencyState.Locked( - onBackClick = onBackClick, - topBarConfig = topBarConfig.updateCallback(), - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig.stopRefreshing(), - onUnlockWalletsNotificationClick = clickIntents::onUnlockWalletNotificationClick, - onUnlockClick = clickIntents::onUnlockWalletClick, - onScanClick = clickIntents::onScanToUnlockWalletClick, - isBalanceHidden = isBalanceHidden, - ) - } - - private fun WalletSingleCurrencyState.Content.toSingleCurrencyLockedState(): WalletState { - return WalletSingleCurrencyState.Locked( - onBackClick = onBackClick, - topBarConfig = topBarConfig.updateCallback(), - walletsListConfig = walletsListConfig, - pullToRefreshConfig = pullToRefreshConfig.stopRefreshing(), - buttons = buttons.disableButtons(), - onUnlockWalletsNotificationClick = clickIntents::onUnlockWalletNotificationClick, - onUnlockClick = clickIntents::onUnlockWalletClick, - onScanClick = clickIntents::onScanToUnlockWalletClick, - onExploreClick = clickIntents::onExploreClick, - isBalanceHidden = isBalanceHidden, - ) - } - - private fun WalletTopBarConfig.updateCallback(): WalletTopBarConfig { - return copy(onDetailsClick = clickIntents::onUnlockWalletNotificationClick) - } - - private fun WalletPullToRefreshConfig.stopRefreshing(): WalletPullToRefreshConfig { - return copy(isRefreshing = false) - } - - private fun PersistentList.disableButtons(): PersistentList { - return this - .map { button -> - when (button) { - is WalletManageButton.Buy -> button.copy(enabled = false) - is WalletManageButton.Sell -> button.copy(enabled = false) - is WalletManageButton.Send -> button.copy(enabled = false) - is WalletManageButton.Swap -> button.copy(enabled = false) - is WalletManageButton.Receive -> button.copy(enabled = false) - } - } - .toPersistentList() - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt deleted file mode 100644 index d0e92be7ec..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.mutate - -internal class WalletRefreshStateConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: Boolean): WalletState { - val state = currentStateProvider() - val contentState = state as? WalletState.ContentState ?: return state - - return if (value) { - contentState.getRefreshingState() - } else { - contentState.getRefreshedState() - } - } - - private fun WalletState.ContentState.getRefreshingState(): WalletState { - return when (this) { - is WalletMultiCurrencyState.Content -> getRefreshingState() - is WalletSingleCurrencyState.Content -> getRefreshingState() - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - -> this - } - } - - private fun WalletState.ContentState.getRefreshedState(): WalletState { - return when (this) { - is WalletMultiCurrencyState.Content -> getRefreshedState() - is WalletSingleCurrencyState.Content -> getRefreshedState() - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - -> this - } - } - - private fun WalletMultiCurrencyState.Content.getRefreshingState(): WalletMultiCurrencyState { - return copy( - pullToRefreshConfig = updatePullToRefreshConfig(isRefreshing = true), - tokensListState = updateTokenListState(isRefreshing = true), - ) - } - - private fun WalletSingleCurrencyState.Content.getRefreshingState(): WalletSingleCurrencyState { - return copy( - pullToRefreshConfig = updatePullToRefreshConfig(isRefreshing = true), - buttons = updateButtons(isRefreshing = true), - ) - } - - private fun WalletMultiCurrencyState.Content.getRefreshedState(): WalletMultiCurrencyState { - return copy( - pullToRefreshConfig = updatePullToRefreshConfig(isRefreshing = false), - tokensListState = updateTokenListState(isRefreshing = false), - ) - } - - private fun WalletSingleCurrencyState.Content.getRefreshedState(): WalletSingleCurrencyState { - return copy( - pullToRefreshConfig = updatePullToRefreshConfig(isRefreshing = false), - buttons = updateButtons(isRefreshing = false), - ) - } - - private fun WalletMultiCurrencyState.updateTokenListState(isRefreshing: Boolean): WalletTokensListState { - return when (val listState = tokensListState) { - is WalletTokensListState.Content -> { - when (listState.organizeTokensButton) { - is WalletTokensListState.OrganizeTokensButtonState.Hidden -> listState - is WalletTokensListState.OrganizeTokensButtonState.Visible -> listState.copy( - organizeTokensButton = listState.organizeTokensButton.copy( - isEnabled = !isRefreshing, - ), - ) - } - } - is WalletTokensListState.Locked, - is WalletTokensListState.Loading, - is WalletTokensListState.Empty, - -> listState - } - } - - private fun WalletSingleCurrencyState.updateButtons(isRefreshing: Boolean): PersistentList { - val isButtonsEnabled = !isRefreshing - - return buttons.mutate { - it.mapNotNull { button -> - when (button) { - is WalletManageButton.Buy -> button.copy(enabled = isButtonsEnabled) - is WalletManageButton.Send -> button.copy(enabled = isButtonsEnabled) - is WalletManageButton.Sell -> button.copy(enabled = isButtonsEnabled) - is WalletManageButton.Receive -> button - is WalletManageButton.Swap -> null - } - } - } - } - - private fun WalletState.ContentState.updatePullToRefreshConfig(isRefreshing: Boolean): WalletPullToRefreshConfig { - return pullToRefreshConfig.copy(isRefreshing = isRefreshing) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRenameStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRenameStateConverter.kt deleted file mode 100644 index aec0b673f7..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRenameStateConverter.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList - -internal class WalletRenameStateConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: String): WalletState { - return when (val state = currentStateProvider()) { - is WalletState.ContentState -> { - state.copySealed( - walletsListConfig = state.walletsListConfig.renameSelectedWallet(name = value), - ) - } - is WalletState.Initial -> state - } - } - - private fun WalletsListConfig.renameSelectedWallet(name: String): WalletsListConfig { - return copy( - wallets = wallets - .mapIndexed { index, walletCard -> - if (index == selectedWalletIndex) walletCard.copySealed(title = name) else walletCard - } - .toImmutableList(), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt deleted file mode 100644 index 75ce18f490..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt +++ /dev/null @@ -1,181 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import arrow.core.Either -import com.tangem.core.ui.components.marketprice.MarketPriceBlockState -import com.tangem.core.ui.components.marketprice.PriceChangeState -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory -import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import com.tangem.feature.wallet.presentation.wallet.utils.CurrencyStatusErrorConverter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toPersistentList -import java.math.BigDecimal - -internal class WalletSingleCurrencyLoadedBalanceConverter( - private val currentStateProvider: Provider, - private val appCurrencyProvider: Provider, - private val currentWalletProvider: Provider, - private val currencyStatusErrorConverter: CurrencyStatusErrorConverter, -) : Converter, WalletState> { - - override fun convert(value: Either): WalletState { - return value.fold( - ifLeft = currencyStatusErrorConverter::convert, - ifRight = ::convertContent, - ) - } - - private fun convertContent(status: CryptoCurrencyStatus): WalletState { - return when (val state = currentStateProvider()) { - is WalletSingleCurrencyState.Content -> { - val currencyName = state.marketPriceBlockState.currencySymbol - - state.copy( - walletsListConfig = getUpdatedSelectedWallet(status = status.value, state = state), - marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName), - ) - } - is WalletMultiCurrencyState.Content, - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> state - } - } - - private fun getMarketPriceState( - status: CryptoCurrencyStatus.Status, - currencySymbol: String, - ): MarketPriceBlockState { - return when (status) { - is CryptoCurrencyStatus.Loaded, - is CryptoCurrencyStatus.NoAmount, - -> status.toContentConfig(currencySymbol) - is CryptoCurrencyStatus.NoAccount -> { - if (status.fiatRate == null) { - MarketPriceBlockState.Error(currencySymbol) - } else { - status.toContentConfig(currencySymbol) - } - } - is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencySymbol) - is CryptoCurrencyStatus.Custom, - is CryptoCurrencyStatus.MissedDerivation, - is CryptoCurrencyStatus.Unreachable, - is CryptoCurrencyStatus.NoQuote, - -> MarketPriceBlockState.Error(currencySymbol) - } - } - - private fun getUpdatedSelectedWallet( - status: CryptoCurrencyStatus.Status, - state: WalletSingleCurrencyState, - ): WalletsListConfig { - val selectedWallet = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex] - - val updatedWallet = when (status) { - is CryptoCurrencyStatus.NoQuote, - is CryptoCurrencyStatus.Loaded, - is CryptoCurrencyStatus.NoAccount, - is CryptoCurrencyStatus.NoAmount, - -> { - WalletCardState.Content( - id = selectedWallet.id, - title = selectedWallet.title, - additionalInfo = WalletAdditionalInfoFactory.resolve( - wallet = currentWalletProvider(), - currencyAmount = status.amount, - ), - imageResId = selectedWallet.imageResId, - onRenameClick = selectedWallet.onRenameClick, - onDeleteClick = selectedWallet.onDeleteClick, - balance = formatFiatAmount(status = status, appCurrency = appCurrencyProvider()), - cardCount = currentWalletProvider().getCardsCount(), - ) - } - is CryptoCurrencyStatus.Loading -> { - WalletCardState.Loading( - id = selectedWallet.id, - title = selectedWallet.title, - imageResId = selectedWallet.imageResId, - onRenameClick = selectedWallet.onRenameClick, - onDeleteClick = selectedWallet.onDeleteClick, - ) - } - is CryptoCurrencyStatus.MissedDerivation, - is CryptoCurrencyStatus.Custom, - is CryptoCurrencyStatus.Unreachable, - -> { - WalletCardState.Error( - id = selectedWallet.id, - title = selectedWallet.title, - imageResId = selectedWallet.imageResId, - onRenameClick = selectedWallet.onRenameClick, - onDeleteClick = selectedWallet.onDeleteClick, - ) - } - } - - return state.walletsListConfig.copy( - wallets = state.walletsListConfig.wallets.toPersistentList() - .set(index = state.walletsListConfig.selectedWalletIndex, element = updatedWallet), - ) - } - - private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content { - return MarketPriceBlockState.Content( - currencySymbol = currencySymbol, - price = formatPrice(status = this, appCurrency = appCurrencyProvider()), - priceChangeConfig = PriceChangeState.Content( - valueInPercent = formatPriceChange(status = this), - type = getPriceChangeType(status = this), - ), - ) - } - - private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType { - val priceChange = status.priceChange ?: return PriceChangeType.DOWN - - return if (priceChange > BigDecimal.ZERO) PriceChangeType.UP else PriceChangeType.DOWN - } - - private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String { - val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - - return BigDecimalFormatter.formatPercent( - percent = priceChange, - useAbsoluteValue = true, - ) - } - - private fun formatPrice(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String { - val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - - return BigDecimalFormatter.formatFiatAmount( - fiatAmount = fiatRate, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) - } - - private fun formatFiatAmount(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String { - val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - - return BigDecimalFormatter.formatFiatAmount( - fiatAmount = fiatAmount, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt deleted file mode 100644 index 8fb80d29d7..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import androidx.annotation.DrawableRes -import com.tangem.core.ui.components.marketprice.MarketPriceBlockState -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory -import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.* -import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletSkeletonStateConverter.SkeletonModel -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow - -/** - * Converter from loaded list of [UserWallet] to skeleton state of screen [WalletState.ContentState] - * - * @property currentStateProvider current ui state provider - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -internal class WalletSkeletonStateConverter( - private val currentStateProvider: Provider, - private val isBalanceHiddenProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: SkeletonModel): WalletState.ContentState { - val selectedWallet = value.wallets[value.selectedWalletIndex] - - val isSingleWalletWithToken = !selectedWallet.isMultiCurrency && - selectedWallet.scanResponse.walletData?.token != null - return if (selectedWallet.isMultiCurrency || isSingleWalletWithToken) { - createMultiCurrencyState(value = value) - } else { - createSingleCurrencyState(value = value, currencyName = selectedWallet.getPrimaryCurrencyName()) - } - } - - private fun createMultiCurrencyState(value: SkeletonModel): WalletMultiCurrencyState { - return WalletMultiCurrencyState.Content( - onBackClick = clickIntents::onBackClick, - topBarConfig = createTopBarConfig(), - walletsListConfig = createWalletsListConfig(value), - pullToRefreshConfig = createPullToRefreshConfig(), - tokensListState = WalletTokensListState.Loading(), - notifications = persistentListOf(), - bottomSheetConfig = null, - onManageTokensClick = clickIntents::onManageTokensClick, - isBalanceHidden = isBalanceHiddenProvider(), - ) - } - - private fun createSingleCurrencyState(value: SkeletonModel, currencyName: String): WalletSingleCurrencyState { - return WalletSingleCurrencyState.Content( - onBackClick = clickIntents::onBackClick, - topBarConfig = createTopBarConfig(), - walletsListConfig = createWalletsListConfig(value), - pullToRefreshConfig = createPullToRefreshConfig(), - notifications = persistentListOf(), - bottomSheetConfig = null, - buttons = createButtons(), - marketPriceBlockState = MarketPriceBlockState.Loading(currencySymbol = currencyName), - txHistoryState = TxHistoryState.Content( - contentItems = MutableStateFlow( - value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), - ), - ), - isBalanceHidden = isBalanceHiddenProvider(), - ) - } - - private fun UserWallet.getPrimaryCurrencyName(): String { - return scanResponse.cardTypesResolver.getBlockchain().currency - } - - private fun createTopBarConfig(): WalletTopBarConfig { - return WalletTopBarConfig(onDetailsClick = clickIntents::onDetailsClick) - } - - private fun createWalletsListConfig(value: SkeletonModel): WalletsListConfig { - return WalletsListConfig( - selectedWalletIndex = value.selectedWalletIndex, - wallets = value.wallets.map(::createWalletCardState).toImmutableList(), - onWalletChange = clickIntents::onWalletChange, - ) - } - - /** - * Create wallet card state by [wallet]. - * If current wallet card state is initialized, then method returns it. - * Otherwise, returns loading wallet card state. - */ - private fun createWalletCardState(wallet: UserWallet): WalletCardState { - return currentStateProvider().getInitializedWalletCardState(wallet.walletId) ?: wallet.mapToWalletCardState() - } - - private fun WalletState.getInitializedWalletCardState(walledId: UserWalletId): WalletCardState? { - return (this as? WalletState.ContentState)?.walletsListConfig?.wallets?.firstOrNull { it.id == walledId } - } - - private fun UserWallet.mapToWalletCardState(): WalletCardState { - return if (isLocked) mapToLockedWalletCardState() else mapToLoadingWalletCardState() - } - - private fun UserWallet.mapToLockedWalletCardState(): WalletCardState { - return WalletCardState.LockedContent( - id = walletId, - title = name, - additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = this), - imageResId = createImageResId(), - onRenameClick = clickIntents::onRenameBeforeConfirmationClick, - onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick, - ) - } - - private fun UserWallet.mapToLoadingWalletCardState(): WalletCardState { - return WalletCardState.Loading( - id = walletId, - title = name, - additionalInfo = if (isMultiCurrency) WalletAdditionalInfoFactory.resolve(wallet = this) else null, - imageResId = createImageResId(), - onRenameClick = clickIntents::onRenameBeforeConfirmationClick, - onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick, - ) - } - - @DrawableRes - private fun UserWallet.createImageResId(): Int? { - return WalletImageResolver.resolve(userWallet = this) - } - - private fun createPullToRefreshConfig(): WalletPullToRefreshConfig { - return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = clickIntents::onRefreshSwipe) - } - - private fun createButtons(): PersistentList { - return persistentListOf( - WalletManageButton.Buy(enabled = false, onClick = {}), - WalletManageButton.Send(enabled = false, onClick = {}), - WalletManageButton.Receive(enabled = false, onClick = {}), - WalletManageButton.Sell(enabled = false, onClick = {}), - ) - } - - data class SkeletonModel(val wallets: List, val selectedWalletIndex: Int) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt deleted file mode 100644 index e803ae15cd..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ /dev/null @@ -1,288 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import androidx.paging.PagingData -import arrow.core.Either -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.event.triggeredEvent -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.TokenActionsState -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.domain.txhistory.models.TxHistoryListError -import com.tangem.domain.txhistory.models.TxHistoryStateError -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.* -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter -import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter -import com.tangem.feature.wallet.presentation.wallet.utils.CurrencyStatusErrorConverter -import com.tangem.feature.wallet.presentation.wallet.utils.HiddenStateConverter -import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.flow.Flow - -/** - * Main factory for creating [WalletState] - * - * @property currentStateProvider current ui state provider - * @property currentCardTypeResolverProvider current card type resolver - * @property currentWalletProvider current wallet - * @property appCurrencyProvider app currency provider - * @property clickIntents screen click intents - */ -@Suppress("TooManyFunctions") -internal class WalletStateFactory( - private val currentStateProvider: Provider, - private val currentCardTypeResolverProvider: Provider, - private val currentWalletProvider: Provider, - private val appCurrencyProvider: Provider, - private val isBalanceHiddenProvider: Provider, - private val clickIntents: WalletClickIntents, -) { - - private val tokenActionsProvider by lazy { TokenActionsProvider(currentWalletProvider, clickIntents) } - - private val skeletonConverter by lazy { - WalletSkeletonStateConverter( - currentStateProvider = currentStateProvider, - isBalanceHiddenProvider = isBalanceHiddenProvider, - clickIntents = clickIntents, - ) - } - - private val walletsUnlockStateConverter by lazy { WalletsUnlockStateConverter(currentStateProvider, clickIntents) } - - private val walletRenameStateConverter by lazy { WalletRenameStateConverter(currentStateProvider) } - - private val walletDeleteStateConverter by lazy { WalletDeleteStateConverter(currentStateProvider) } - - private val hiddenStateConverter by lazy { HiddenStateConverter(currentStateProvider) } - - private val walletUpdateCardCountConverter by lazy { - WalletUpdateCardCountConverter( - currentStateProvider, - currentWalletProvider, - ) - } - - private val tokenListErrorConverter by lazy { - TokenListErrorConverter(currentStateProvider) - } - private val currencyStatusErrorConverter by lazy { - CurrencyStatusErrorConverter(currentStateProvider) - } - private val loadedTokensListConverter by lazy { - WalletLoadedTokensListConverter( - currentStateProvider = currentStateProvider, - tokenListErrorConverter = tokenListErrorConverter, - appCurrencyProvider = appCurrencyProvider, - currentWalletProvider = currentWalletProvider, - clickIntents = clickIntents, - ) - } - - private val loadingTransactionsStateConverter by lazy { - WalletLoadingTxHistoryConverter( - currentStateProvider = currentStateProvider, - currentCardTypeResolverProvider = currentCardTypeResolverProvider, - clickIntents = clickIntents, - ) - } - - private val loadedTxHistoryConverter by lazy { - WalletLoadedTxHistoryConverter( - currentStateProvider = currentStateProvider, - currentCardTypeResolverProvider = currentCardTypeResolverProvider, - clickIntents = clickIntents, - ) - } - - private val singleCurrencyLoadedBalanceConverter by lazy { - WalletSingleCurrencyLoadedBalanceConverter( - currentStateProvider = currentStateProvider, - appCurrencyProvider = appCurrencyProvider, - currentWalletProvider = currentWalletProvider, - currencyStatusErrorConverter = currencyStatusErrorConverter, - ) - } - - private val lockedConverter by lazy { - WalletLockedConverter( - currentStateProvider = currentStateProvider, - clickIntents = clickIntents, - ) - } - - private val refreshStateConverter by lazy { - WalletRefreshStateConverter(currentStateProvider) - } - - private val cryptoCurrencyActionsConverter by lazy { - WalletCryptoCurrencyActionsConverter( - currentWalletProvider = currentWalletProvider, - currentStateProvider = currentStateProvider, - clickIntents = clickIntents, - ) - } - - fun getInitialState(): WalletState = WalletState.Initial(onBackClick = clickIntents::onBackClick) - - fun getSkeletonState(wallets: List, selectedWalletIndex: Int): WalletState { - return skeletonConverter.convert( - value = WalletSkeletonStateConverter.SkeletonModel( - wallets = wallets, - selectedWalletIndex = selectedWalletIndex, - ), - ) - } - - fun getStateWithUpdatedWalletName(name: String): WalletState = walletRenameStateConverter.convert(value = name) - - fun getStateWithUpdatedWalletCardCount(): WalletState = walletUpdateCardCountConverter.convert(Unit) - - fun getUnlockedState(action: WalletsUpdateActionResolver.Action.UnlockWallet): WalletState { - return walletsUnlockStateConverter.convert(value = action) - } - - fun getStateWithoutDeletedWallet( - cacheState: WalletState.ContentState, - action: WalletsUpdateActionResolver.Action.DeleteWallet, - ): WalletState { - return walletDeleteStateConverter.convert( - value = WalletDeleteStateConverter.DeleteWalletModel(cacheState = cacheState, action = action), - ) - } - - fun getStateByTokensList(maybeTokenListWithWallet: Either): WalletState { - return loadedTokensListConverter.convert(maybeTokenListWithWallet) - } - - fun getStateByTokenListError(error: TokenListError): WalletState { - return tokenListErrorConverter.convert(error) - } - - fun getStateByNotifications(notifications: ImmutableList): WalletState { - return when (val state = currentStateProvider()) { - is WalletMultiCurrencyState.Content -> state.copy(notifications = notifications) - is WalletSingleCurrencyState.Content -> state.copy(notifications = notifications) - else -> state - } - } - - fun getRefreshingState(): WalletState = refreshStateConverter.convert(value = true) - - fun getRefreshedState(): WalletState = refreshStateConverter.convert(value = false) - - fun getStateWithOpenWalletBottomSheet(content: TangemBottomSheetConfigContent): WalletState { - return when (val state = currentStateProvider() as WalletState.ContentState) { - is WalletMultiCurrencyState.Content -> state.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = clickIntents::onDismissBottomSheet, - content = content, - ), - ) - is WalletMultiCurrencyState.Locked -> state.copy( - isBottomSheetShow = true, - onBottomSheetDismiss = clickIntents::onDismissBottomSheet, - ) - is WalletSingleCurrencyState.Content -> state.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = clickIntents::onDismissBottomSheet, - content = content, - ), - ) - is WalletSingleCurrencyState.Locked -> state.copy( - isBottomSheetShow = true, - onBottomSheetDismiss = clickIntents::onDismissBottomSheet, - ) - } - } - - fun getStateWithClosedBottomSheet(): WalletState { - return when (val state = currentStateProvider() as WalletState.ContentState) { - is WalletMultiCurrencyState.Content -> state.copy( - bottomSheetConfig = state.bottomSheetConfig?.copy(isShow = false), - ) - is WalletMultiCurrencyState.Locked -> state.copy(isBottomSheetShow = false) - is WalletSingleCurrencyState.Content -> state.copy( - bottomSheetConfig = state.bottomSheetConfig?.copy(isShow = false), - ) - is WalletSingleCurrencyState.Locked -> state.copy(isBottomSheetShow = false) - } - } - - fun getStateWithTokenActionBottomSheet(tokenActions: TokenActionsState): WalletState { - return getStateWithOpenWalletBottomSheet( - content = ActionsBottomSheetConfig(actions = tokenActionsProvider.provideActions(tokenActions)), - ) - } - - fun getLoadingTxHistoryState( - itemsCountEither: Either, - pendingTransactions: Set, - ): WalletState { - return loadingTransactionsStateConverter.convert( - WalletLoadingTxHistoryConverter.WalletLoadingTxHistoryModel( - historyLoadingState = itemsCountEither, - pendingTransactions = pendingTransactions, - ), - ) - } - - fun getLoadedTxHistoryState( - txHistoryEither: Either>>, - ): WalletState { - return loadedTxHistoryConverter.convert(txHistoryEither) - } - - fun getLockedState(): WalletState = lockedConverter.convert(Unit) - - fun getSingleCurrencyLoadedBalanceState( - maybeCryptoCurrencyStatus: Either, - ): WalletState { - return singleCurrencyLoadedBalanceConverter.convert(maybeCryptoCurrencyStatus) - } - - fun getSingleCurrencyManageButtonsState(actionsState: TokenActionsState): WalletState { - return cryptoCurrencyActionsConverter.convert(value = actionsState) - } - - fun getStateByCurrencyStatusError(error: CurrencyStatusError): WalletState { - return currencyStatusErrorConverter.convert(error) - } - - fun getHiddenBalanceState(isBalanceHidden: Boolean): WalletState { - return hiddenStateConverter.convert(isBalanceHidden) - } - - fun getStateAndTriggerEvent( - state: WalletState, - event: WalletEvent, - setUiState: (WalletState) -> Unit, - ): WalletState { - return when (state) { - is WalletState.ContentState -> state.copySealed( - event = triggeredEvent( - data = event, - onConsume = { - val currentState = currentStateProvider() - if (currentState is WalletState.ContentState) { - setUiState(currentState.copySealed(event = consumedEvent())) - } - }, - ), - ) - is WalletState.Initial -> state - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletUpdateCardCountConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletUpdateCardCountConverter.kt deleted file mode 100644 index 3092c454c6..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletUpdateCardCountConverter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory -import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver -import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList - -internal class WalletUpdateCardCountConverter( - private val currentStateProvider: Provider, - private val currentWalletProvider: Provider, -) : Converter { - - override fun convert(value: Unit): WalletState { - return when (val state = currentStateProvider()) { - is WalletState.ContentState -> { - state.copySealed( - walletsListConfig = state.walletsListConfig.refreshCardCount(), - ) - } - is WalletState.Initial -> state - } - } - - private fun WalletsListConfig.refreshCardCount(): WalletsListConfig { - val selectedWallet = currentWalletProvider() - return copy( - wallets = wallets - .mapIndexed { index, walletCard -> - if (index == selectedWalletIndex) { - when (walletCard) { - is WalletCardState.Content -> walletCard.copy( - additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = selectedWallet), - imageResId = WalletImageResolver.resolve(userWallet = selectedWallet), - cardCount = selectedWallet.getCardsCount(), - ) - else -> walletCard - } - } else { - walletCard - } - } - .toImmutableList(), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletsUnlockStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletsUnlockStateConverter.kt deleted file mode 100644 index 4dc8b76098..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletsUnlockStateConverter.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory - -import com.tangem.core.ui.components.marketprice.MarketPriceBlockState -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver.Action.UnlockWallet as UnlockWalletAction - -/** - * Converter that responds on wallets unlocking action. Returns [WalletState] with unlocked wallets. - * - * @property currentStateProvider current ui state provider - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -internal class WalletsUnlockStateConverter( - private val currentStateProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: UnlockWalletAction): WalletState { - return when (val state = currentStateProvider()) { - is WalletMultiCurrencyState.Locked -> state.toMultiCurrencyContentState(value) - is WalletSingleCurrencyState.Locked -> state.toSingleCurrencyContentState(value) - is WalletState.Initial, - is WalletMultiCurrencyState.Content, - is WalletSingleCurrencyState.Content, - -> state - } - } - - private fun WalletMultiCurrencyState.Locked.toMultiCurrencyContentState(action: UnlockWalletAction): WalletState { - return WalletMultiCurrencyState.Content( - onBackClick = onBackClick, - topBarConfig = topBarConfig.updateCallback(), - walletsListConfig = walletsListConfig.unlockWallets(action), - pullToRefreshConfig = pullToRefreshConfig.stopRefreshing(), - tokensListState = WalletTokensListState.Loading(), - notifications = persistentListOf(), - bottomSheetConfig = null, - onManageTokensClick = clickIntents::onManageTokensClick, - isBalanceHidden = isBalanceHidden, - ) - } - - private fun WalletSingleCurrencyState.Locked.toSingleCurrencyContentState(action: UnlockWalletAction): WalletState { - return WalletSingleCurrencyState.Content( - onBackClick = onBackClick, - topBarConfig = topBarConfig.updateCallback(), - walletsListConfig = walletsListConfig.unlockWallets(action), - pullToRefreshConfig = pullToRefreshConfig.stopRefreshing(), - notifications = persistentListOf(), - bottomSheetConfig = null, - buttons = buttons, - marketPriceBlockState = MarketPriceBlockState.Loading( - currencySymbol = action.selectedWallet.getPrimaryCurrencyName(), - ), - txHistoryState = TxHistoryState.Content( - contentItems = MutableStateFlow( - value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), - ), - ), - isBalanceHidden = isBalanceHidden, - ) - } - - private fun WalletTopBarConfig.updateCallback(): WalletTopBarConfig { - return copy(onDetailsClick = clickIntents::onDetailsClick) - } - - private fun WalletsListConfig.unlockWallets(action: UnlockWalletAction): WalletsListConfig { - return this.copy( - selectedWalletIndex = action.selectedWalletIndex, - wallets = wallets.unlockWallets(action), - ) - } - - private fun List.unlockWallets(action: UnlockWalletAction): ImmutableList { - return this - .map { prevWallet -> - if (prevWallet is WalletCardState.LockedContent && action.isUnlockedWallet(prevWallet.id)) { - prevWallet.mapToLoadingWalletCardState( - userWallet = action.getUnlockWallet(prevWallet.id), - ) - } else { - prevWallet - } - } - .toImmutableList() - } - - private fun UnlockWalletAction.isUnlockedWallet(walletId: UserWalletId): Boolean { - return unlockedWallets.any { it.walletId == walletId } - } - - private fun UnlockWalletAction.getUnlockWallet(walletId: UserWalletId): UserWallet { - return unlockedWallets.firstOrNull { it.walletId == walletId } - ?: error("Unlocked wallet with id $walletId not found") - } - - private fun WalletCardState.mapToLoadingWalletCardState(userWallet: UserWallet): WalletCardState { - return WalletCardState.Loading( - id = id, - title = title, - imageResId = WalletImageResolver.resolve(userWallet = userWallet), - onRenameClick = onRenameClick, - onDeleteClick = onDeleteClick, - ) - } - - private fun WalletPullToRefreshConfig.stopRefreshing(): WalletPullToRefreshConfig { - return copy(isRefreshing = false) - } - - private fun UserWallet.getPrimaryCurrencyName(): String { - return scanResponse.cardTypesResolver.getBlockchain().currency - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt deleted file mode 100644 index d2fd2b4f17..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory - -import androidx.paging.PagingData -import arrow.core.Either -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.domain.txhistory.models.TxHistoryListError -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.coroutines.flow.Flow - -/** - * Converter from loaded tx history to [TxHistoryState] - * - * @property currentStateProvider current state provider - * @property currentCardTypeResolverProvider current card type resolver provider - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -internal class WalletLoadedTxHistoryConverter( - private val currentStateProvider: Provider, - private val currentCardTypeResolverProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter>>, WalletState> { - - private val walletTxHistoryItemFlowConverter by lazy { - WalletTxHistoryItemFlowConverter( - currentStateProvider = currentStateProvider, - blockchain = currentCardTypeResolverProvider().getBlockchain(), - clickIntents = clickIntents, - ) - } - - override fun convert(value: Either>>): WalletState { - return value.fold(ifLeft = ::convertError, ifRight = ::convert) - } - - private fun convertError(error: TxHistoryListError): WalletState { - return when (val state = currentStateProvider()) { - is WalletSingleCurrencyState.Content -> { - state.copy( - txHistoryState = when (error) { - is TxHistoryListError.DataError -> { - TxHistoryState.Error( - onReloadClick = clickIntents::onReloadClick, - onExploreClick = clickIntents::onExploreClick, - ) - } - }, - ) - } - is WalletMultiCurrencyState.Content, - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> state - } - } - - private fun convert(items: Flow>): WalletState { - return when (val state = currentStateProvider()) { - is WalletSingleCurrencyState.Content -> { - return state.copy( - txHistoryState = walletTxHistoryItemFlowConverter.convert(value = items) ?: state.txHistoryState, - ) - } - is WalletMultiCurrencyState.Content, - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> state - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt deleted file mode 100644 index ff688613a4..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory - -import androidx.paging.PagingData -import arrow.core.Either -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.components.transactions.state.TxHistoryState.* -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.domain.txhistory.models.TxHistoryStateError -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update - -/** - * Converter from loading tx history state to [WalletSingleCurrencyState.Content] - * - * @property currentStateProvider current state provider - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -internal class WalletLoadingTxHistoryConverter( - private val currentStateProvider: Provider, - private val currentCardTypeResolverProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - private val txHistoryItemConverter by lazy { - val blockchain = currentCardTypeResolverProvider().getBlockchain() - WalletPendingTxToTransactionStateConverter( - symbol = blockchain.currency, - decimals = blockchain.decimals(), - clickIntents = clickIntents, - ) - } - - override fun convert(value: WalletLoadingTxHistoryModel): WalletState { - return value.historyLoadingState.fold( - ifLeft = { convertError(it, value.pendingTransactions) }, - ifRight = ::convertRight, - ) - } - - private fun convertError(error: TxHistoryStateError, pendingTransactions: Set): WalletState { - val state = currentStateProvider() - - return if (state is WalletSingleCurrencyState.Content) { - state.copy( - txHistoryState = when (error) { - is TxHistoryStateError.EmptyTxHistories -> Empty(onExploreClick = clickIntents::onExploreClick) - is TxHistoryStateError.DataError -> Error( - onReloadClick = clickIntents::onReloadClick, - onExploreClick = clickIntents::onExploreClick, - ) - is TxHistoryStateError.TxHistoryNotImplemented -> { - NotSupported( - pendingTransactions = txHistoryItemConverter.convertList(pendingTransactions) - .toImmutableList(), - onExploreClick = clickIntents::onExploreClick, - ) - } - }, - ) - } else { - state - } - } - - private fun convertRight(value: Int): WalletState { - val state = currentStateProvider() - val singleCurrencyContentState = state as? WalletSingleCurrencyState.Content ?: return state - return if (singleCurrencyContentState.txHistoryState is Content) { - singleCurrencyContentState.txHistoryState.contentItems.update { - PagingData.from(data = createLoadingItems(value)) - } - state - } else { - val txHistoryContent = Content( - contentItems = MutableStateFlow( - value = PagingData.from(data = createLoadingItems(value)), - ), - ) - state.copy(txHistoryState = txHistoryContent) - } - } - - private fun createLoadingItems(size: Int): List { - return buildList { - add(TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick)) - (1..size).forEach { - add(TxHistoryItemState.Transaction(state = TransactionState.Loading(it.toString()))) - } - } - } - - data class WalletLoadingTxHistoryModel( - val historyLoadingState: Either, - val pendingTransactions: Set, - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletPendingTxToTransactionStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletPendingTxToTransactionStateConverter.kt deleted file mode 100644 index a3d2cfa92e..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletPendingTxToTransactionStateConverter.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory - -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.converter.Converter -import com.tangem.utils.toBriefAddressFormat -import com.tangem.utils.toFormattedCurrencyString -import org.joda.time.DateTime -import org.joda.time.DateTimeZone - -// FIXME: Refactoring needed -/** Same as [WalletTxHistoryTransactionStateConverter] but with other timestamp format */ -internal class WalletPendingTxToTransactionStateConverter( - private val symbol: String, - private val decimals: Int, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: TxHistoryItem): TransactionState { - return createTransactionStateItem(item = value) - } - - private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { - return TransactionState.Content( - txHash = item.txHash, - amount = item.getAmount(), - timestamp = item.timestampInMillis.toTimeFormat(), - status = item.status.tiUiStatus(), - direction = item.extractDirection(), - iconRes = item.extractIcon(), - title = item.extractTitle(), - subtitle = item.extractSubtitle(), - onClick = { clickIntents.onTransactionClick(item.txHash) }, - ) - } - - private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) { - R.drawable.ic_close_24 - } else { - when (type) { - is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24 - is TxHistoryItem.TransactionType.Operation, - is TxHistoryItem.TransactionType.Swap, - is TxHistoryItem.TransactionType.Transfer, - is TxHistoryItem.TransactionType.UnknownOperation, - -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 - } - } - - private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) { - is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval) - is TxHistoryItem.TransactionType.Operation -> stringReference(type.name) - is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap) - is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) - } - - private fun TxHistoryItem.extractSubtitle(): TextReference = - when (val interactionAddress = interactionAddressType) { - is TxHistoryItem.InteractionAddressType.Contract -> resourceReference( - id = R.string.transaction_history_contract_address, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)), - ) - is TxHistoryItem.InteractionAddressType.User -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - } - - private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { - TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed - TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed - TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed - } - - private fun Long.toTimeFormat(): String { - return DateTimeFormatters.formatTime(time = DateTime(this, DateTimeZone.getDefault())) - } - - private fun TxHistoryItem.extractDirection() = - if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING - - private fun TxHistoryItem.getAmount(): String { - val prefix = when (status) { - TxHistoryItem.TransactionStatus.Failed -> "" - else -> if (isOutgoing) "-" else "+" - } - return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt deleted file mode 100644 index 91864cc3ae..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory - -import android.text.format.DateUtils -import androidx.paging.* -import com.tangem.blockchain.common.Blockchain -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import com.tangem.utils.extensions.isToday -import com.tangem.utils.extensions.isYesterday -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* -import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import java.util.UUID - -/** - * Convert from [Flow] of [TxHistoryItem] to [TxHistoryState] - * - * @property currentStateProvider current state provider - * @property blockchain blockchain of transactions history - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -internal class WalletTxHistoryItemFlowConverter( - private val currentStateProvider: Provider, - private val blockchain: Blockchain, - private val clickIntents: WalletClickIntents, -) : Converter>, TxHistoryState?> { - - private val txHistoryItemConverter by lazy { - WalletTxHistoryTransactionStateConverter( - symbol = blockchain.currency, - decimals = blockchain.decimals(), - clickIntents = clickIntents, - ) - } - - override fun convert(value: Flow>): TxHistoryState? { - val state = currentStateProvider() as? WalletSingleCurrencyState ?: return null - val txHistoryContent = state.txHistoryState as? TxHistoryState.Content - ?: TxHistoryState.Content(contentItems = MutableStateFlow(PagingData.empty())) - - // FIXME: TxHistoryRepository should send loading transactions - // [REDACTED_JIRA] - value - .onEach { txHistoryStatePagingData -> - txHistoryContent.contentItems.update { - txHistoryStatePagingData - .map { item -> - // [createTransactionState] returns timestamp without formatting - TxHistoryItemState.Transaction(state = createTransactionState(item)) - } - .insertHeaderItem( - terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE, - item = TxHistoryItemState.Title(clickIntents::onExploreClick), - ) - .insertGroupTitle() // method uses the raw timestamp - .formatTransactionsTimestamp() // method formats the timestamp - } - } - .launchIn(CoroutineScope(Dispatchers.IO)) - - return txHistoryContent - } - - private fun createTransactionState(item: TxHistoryItem): TransactionState { - return txHistoryItemConverter.convert(value = item) - } - - private fun PagingData.insertGroupTitle(): PagingData { - return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after -> - // Use raw timestamp to get date - - // If [afterDate] is the first transaction in the flow, add the group title - val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null - if (before is TxHistoryItemState.Title) { - return@insertSeparators TxHistoryItemState.GroupTitle( - title = afterDate, - itemKey = UUID.randomUUID().toString(), - ) - } - - /* - * If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in - * the new group - */ - val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null - return@insertSeparators if (beforeDate != afterDate) { - TxHistoryItemState.GroupTitle(title = afterDate, itemKey = UUID.randomUUID().toString()) - } else { - null - } - } - } - - /** - * Map the [PagingData] to format the [TxHistoryItemState] timestamp - */ - private fun PagingData.formatTransactionsTimestamp(): PagingData { - return map { txHistoryItemState -> - if (txHistoryItemState is TxHistoryItemState.Transaction && - txHistoryItemState.state is TransactionState.Content - ) { - val txContent = txHistoryItemState.state as TransactionState.Content - txHistoryItemState.copy( - state = txContent.copy(timestamp = txContent.timestamp.toTimeFormat()), - ) - } else { - txHistoryItemState - } - } - } - - private fun TxHistoryItemState?.getTimestamp(): Long? { - return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) { - val txContent = this.state as TransactionState.Content - requireNotNull(txContent.timestamp.toLongOrNull()) { "Timestamp must be Long type" } - } else { - null - } - } - - /** - * If [this] timestamp is today or yesterday, returns relative date, - * otherwise returns formatting date. - */ - private fun Long.toDateFormat(): String { - val localDate = DateTime(this, DateTimeZone.getDefault()) - return if (localDate.isToday() || localDate.isYesterday()) { - DateUtils.getRelativeTimeSpanString( - this, - DateTime.now().millis, - DateUtils.DAY_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE, - ).toString() - } else { - DateTimeFormatters.formatDate(date = localDate) - } - } - - private fun String.toTimeFormat(): String { - return DateTimeFormatters.formatTime(time = DateTime(this.toLong(), DateTimeZone.getDefault())) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryTransactionStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryTransactionStateConverter.kt deleted file mode 100644 index a7b4c8efa5..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryTransactionStateConverter.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory - -import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.converter.Converter -import com.tangem.utils.toBriefAddressFormat -import com.tangem.utils.toFormattedCurrencyString - -// FIXME: Refactoring needed -/** Same as [WalletPendingTxToTransactionStateConverter] but with other timestamp format */ -internal class WalletTxHistoryTransactionStateConverter( - private val symbol: String, - private val decimals: Int, - private val clickIntents: WalletClickIntents, -) : Converter { - - override fun convert(value: TxHistoryItem): TransactionState { - return createTransactionStateItem(item = value) - } - - @Suppress("LongMethod") - private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { - return TransactionState.Content( - txHash = item.txHash, - amount = item.getAmount(), - timestamp = item.getRawTimestamp(), - status = item.status.tiUiStatus(), - direction = item.extractDirection(), - iconRes = item.extractIcon(), - title = item.extractTitle(), - subtitle = item.extractSubtitle(), - onClick = { clickIntents.onTransactionClick(item.txHash) }, - ) - } - - private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) { - R.drawable.ic_close_24 - } else { - when (type) { - is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24 - is TxHistoryItem.TransactionType.Operation, - is TxHistoryItem.TransactionType.Swap, - is TxHistoryItem.TransactionType.Transfer, - is TxHistoryItem.TransactionType.UnknownOperation, - -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 - } - } - - private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) { - is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval) - is TxHistoryItem.TransactionType.Operation -> stringReference(type.name) - is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap) - is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) - } - - private fun TxHistoryItem.extractSubtitle(): TextReference = - when (val interactionAddress = interactionAddressType) { - is TxHistoryItem.InteractionAddressType.Contract -> resourceReference( - id = R.string.transaction_history_contract_address, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)), - ) - is TxHistoryItem.InteractionAddressType.User -> resourceReference( - id = if (isOutgoing) { - R.string.transaction_history_transaction_to_address - } else { - R.string.transaction_history_transaction_from_address - }, - formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()), - ) - } - - private fun TxHistoryItem.extractDirection() = - if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING - - /** - * Get timestamp without formatting. - * It's life hack that help us to add transaction's group title to flow. - * - * @see [convert] - */ - private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString() - - private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { - TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed - TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed - TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed - } - - private fun TxHistoryItem.getAmount(): String { - val prefix = when (status) { - TxHistoryItem.TransactionStatus.Failed -> "" - else -> if (isOutgoing) "-" else "+" - } - return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/ActionsBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ActionsBottomSheetConfig.kt similarity index 84% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/ActionsBottomSheetConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ActionsBottomSheetConfig.kt index 42cf37464b..84ec1221bd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/ActionsBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ActionsBottomSheetConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state +package com.tangem.feature.wallet.presentation.wallet.state.model import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import kotlinx.collections.immutable.ImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBlockState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBlockState.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBlockState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBlockState.kt index a64e659900..85ebece2af 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBlockState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBlockState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt similarity index 78% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBottomSheetConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt index dc15fc27b1..bfabae7770 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/BalancesAndLimitsBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/BalancesAndLimitsBottomSheetConfig.kt @@ -1,13 +1,10 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent internal data class BalancesAndLimitsBottomSheetConfig( - val currency: String, val balance: Balance, val limit: Limit, - val onBalanceInfoClick: () -> Unit, - val onLimitInfoClick: () -> Unit, ) : TangemBottomSheetConfigContent { data class Balance( @@ -17,6 +14,7 @@ internal data class BalancesAndLimitsBottomSheetConfig( val debit: String, val pending: String, val amlVerified: String, + val onInfoClick: () -> Unit, ) data class Limit( @@ -24,5 +22,6 @@ internal data class BalancesAndLimitsBottomSheetConfig( val inStore: String, val other: String, val singleTransaction: String, + val onInfoClick: () -> Unit, ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/DepositButtonState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/DepositButtonState.kt similarity index 59% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/DepositButtonState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/DepositButtonState.kt index 9577abc99e..9a490bf52e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/DepositButtonState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/DepositButtonState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model internal data class DepositButtonState( val isEnabled: Boolean, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/ManageTokensButtonConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ManageTokensButtonConfig.kt similarity index 51% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/ManageTokensButtonConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ManageTokensButtonConfig.kt index 0548298dc0..52c4b9f6dc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/ManageTokensButtonConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/ManageTokensButtonConfig.kt @@ -1,3 +1,3 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model internal data class ManageTokensButtonConfig(val onClick: () -> Unit) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/TokenActionButtonConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonConfig.kt similarity index 88% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/TokenActionButtonConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonConfig.kt index db94522760..12c3c648f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/TokenActionButtonConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.annotation.DrawableRes import com.tangem.core.ui.extensions.TextReference diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/VisaTxDetailsBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/VisaTxDetailsBottomSheetConfig.kt new file mode 100644 index 0000000000..6389eb3a0f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/VisaTxDetailsBottomSheetConfig.kt @@ -0,0 +1,39 @@ +package com.tangem.feature.wallet.presentation.wallet.state.model + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import kotlinx.collections.immutable.ImmutableList + +internal data class VisaTxDetailsBottomSheetConfig( + val transaction: Transaction, + val requests: ImmutableList, +) : TangemBottomSheetConfigContent { + + data class Transaction( + val id: String, + val type: String, + val status: String, + val blockchainAmount: String, + val blockchainFee: String, + val transactionAmount: String, + val transactionCurrencyCode: String, + val merchantName: String, + val merchantCity: String, + val merchantCountryCode: String, + val merchantCategoryCode: String, + ) + + data class Request( + val id: String, + val type: String, + val status: String, + val blockchainAmount: String, + val blockchainFee: String, + val transactionAmount: String, + val currencyCode: String, + val errorCode: Int, + val date: String, + val txHash: String, + val txStatus: String, + val onExploreClick: (() -> Unit)?, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletAdditionalInfo.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt similarity index 73% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletAdditionalInfo.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt index 612f2111e5..2e210c2221 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletAdditionalInfo.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt similarity index 98% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt index 47a7834251..2b1df72669 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBottomSheetConfig.kt similarity index 95% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletBottomSheetConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBottomSheetConfig.kt index 31291aabe5..d6ec5fcc93 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBottomSheetConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.annotation.DrawableRes import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt similarity index 98% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt index 9b06ec02ca..d4feb6a295 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletCardState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt similarity index 92% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt index a269d87651..5ee98fda79 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt similarity index 97% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index 797aaa2711..124d3bf6a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt similarity index 99% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index 6feefe3150..e7c5e885c4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationConfig diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletPullToRefreshConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt similarity index 78% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletPullToRefreshConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt index 6714af85cc..ca91846374 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletPullToRefreshConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model /** * Wallet screen top bar config diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletScreenState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt similarity index 63% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletScreenState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt index 8b092c830c..b4a5a8dc25 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletScreenState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt @@ -1,8 +1,6 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model import com.tangem.core.ui.event.StateEvent -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig import kotlinx.collections.immutable.ImmutableList internal data class WalletScreenState( @@ -13,4 +11,5 @@ internal data class WalletScreenState( val onWalletChange: (Int) -> Unit, val event: StateEvent, val isHidingMode: Boolean, + val manageTokenRedesignToggle: Boolean, ) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt similarity index 86% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index f3b70aa395..0792d8f25a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -1,17 +1,13 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.holder.LockedTxHistoryStateHolder -import com.tangem.feature.wallet.presentation.wallet.state2.model.holder.LockedWalletStateHolder -import com.tangem.feature.wallet.presentation.wallet.state2.model.holder.TxHistoryStateHolder -import com.tangem.feature.wallet.presentation.wallet.state2.model.holder.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedTxHistoryStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedWalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.model.holder.WalletStateHolder import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt similarity index 96% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletTokensListState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt index 0cddc53540..e861c77579 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model +package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTopBarConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTopBarConfig.kt similarity index 72% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTopBarConfig.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTopBarConfig.kt index 9b3cf42edf..0bf162e9c7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTopBarConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTopBarConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state.components +package com.tangem.feature.wallet.presentation.wallet.state.model /** * Wallet screen top bar config diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/LockedWalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt similarity index 75% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/LockedWalletStateHolder.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt index c54e99c560..6c361d5da7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/LockedWalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt @@ -1,10 +1,10 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model.holder +package com.tangem.feature.wallet.presentation.wallet.state.model.holder import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletPullToRefreshConfig import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/TxHistoryStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/TxHistoryStateHolder.kt similarity index 92% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/TxHistoryStateHolder.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/TxHistoryStateHolder.kt index 3094cfe505..0350800f16 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/model/holder/TxHistoryStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/TxHistoryStateHolder.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.model.holder +package com.tangem.feature.wallet.presentation.wallet.state.model.holder import androidx.paging.PagingData import com.tangem.core.ui.components.transactions.state.TransactionState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/AddWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt similarity index 65% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/AddWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt index 5100664912..dd9b3ee7d1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/AddWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt @@ -1,14 +1,14 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.toImmutableList internal class AddWalletTransformer( private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/CloseBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt similarity index 87% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/CloseBottomSheetTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt index 28e7b364c6..81106b0db4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/CloseBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt @@ -1,7 +1,7 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/DeleteWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/DeleteWalletTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/DeleteWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/DeleteWalletTransformer.kt index f36010a664..3c2590c4da 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/DeleteWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/DeleteWalletTransformer.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import kotlinx.collections.immutable.toImmutableList import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/InitializeWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/InitializeWalletsTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt index a191f69f72..7b94f4d770 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/InitializeWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt @@ -1,16 +1,12 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.state2.utils.createStateByWalletType -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.* +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory +import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -19,7 +15,7 @@ internal class InitializeWalletsTransformer( private val selectedWalletIndex: Int, private val selectedWallet: UserWallet, private val wallets: List, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/OpenBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt similarity index 93% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/OpenBottomSheetTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt index 9d6737e940..141f4a57d0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/OpenBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState internal class OpenBottomSheetTransformer( userWalletId: UserWalletId, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ReinitializeWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt similarity index 66% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ReinitializeWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt index dac563b4cc..7caf5d79e0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ReinitializeWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.persistentListOf /** @@ -11,7 +11,7 @@ import kotlinx.collections.immutable.persistentListOf */ internal class ReinitializeWalletTransformer( private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/RenameWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletTransformer.kt similarity index 88% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/RenameWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletTransformer.kt index b218f47ee2..f0b5cf9e20 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/RenameWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/RenameWalletTransformer.kt @@ -1,7 +1,7 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import timber.log.Timber internal class RenameWalletTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ScrollToWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ScrollToWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt index 2651e80ebe..6381c98d9d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/ScrollToWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.utils.Provider internal class ScrollToWalletTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SendEventTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SendEventTransformer.kt similarity index 62% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SendEventTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SendEventTransformer.kt index c56f4d9918..4b9025e588 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SendEventTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SendEventTransformer.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.event.triggeredEvent -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState internal class SendEventTransformer( private val event: WalletEvent, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetBalancesAndLimitsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt similarity index 84% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetBalancesAndLimitsTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt index 9af5df2c08..ea53efb4c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetBalancesAndLimitsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import arrow.core.Either import arrow.core.getOrElse @@ -7,18 +7,18 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletAdditionalInfo -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBlockState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import org.joda.time.DateTime import org.joda.time.Days internal class SetBalancesAndLimitsTransformer( private val userWallet: UserWallet, private val maybeVisaCurrency: Either, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { @@ -26,12 +26,14 @@ internal class SetBalancesAndLimitsTransformer( val visaCurrency = maybeVisaCurrency.getOrElse { return state.copy( walletCardState = getErrorWalletCardState(state.walletCardState), + depositButtonState = state.depositButtonState.copy(isEnabled = false), balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error, ) } state.copy( walletCardState = getContentWalletCardState(state.walletCardState, visaCurrency), + depositButtonState = state.depositButtonState.copy(isEnabled = true), balancesAndLimitBlockState = getContentBlockState(visaCurrency), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetCryptoCurrencyActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt similarity index 91% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetCryptoCurrencyActionsTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt index 88ebcb4e14..e7fedeba18 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetCryptoCurrencyActionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt @@ -1,11 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import timber.log.Timber @@ -13,7 +13,7 @@ import timber.log.Timber internal class SetCryptoCurrencyActionsTransformer( private val tokenActionsState: TokenActionsState, private val userWallet: UserWallet, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetPrimaryCurrencyTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetPrimaryCurrencyTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetPrimaryCurrencyTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetPrimaryCurrencyTransformer.kt index da21391c07..056d47e276 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetPrimaryCurrencyTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetPrimaryCurrencyTransformer.kt @@ -1,13 +1,13 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletCardStateConverter -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletMarketPriceConverter +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletCardStateConverter +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletMarketPriceConverter import timber.log.Timber internal class SetPrimaryCurrencyTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetRefreshStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt similarity index 84% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetRefreshStateTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt index 315d64ab8f..0f6d520acf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetRefreshStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt @@ -1,12 +1,7 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBlockState -import com.tangem.feature.wallet.presentation.wallet.state2.model.DepositButtonState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.model.* import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListErrorTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt index 587ef5d25d..046b5fd498 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState import timber.log.Timber internal class SetTokenListErrorTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt similarity index 75% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 65c3cff6d7..b852f07fa7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -1,23 +1,23 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.model.ManageTokensButtonConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.MultiWalletCardStateConverter -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TokenListStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.ManageTokensButtonConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import timber.log.Timber internal class SetTokenListTransformer( private val tokenList: TokenList, private val userWallet: UserWallet, private val appCurrency: AppCurrency, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt similarity index 87% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountErrorTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt index 745028a37a..9d97991b83 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt @@ -1,13 +1,13 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TxHistoryItemStateConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.toImmutableList import timber.log.Timber @@ -15,7 +15,7 @@ internal class SetTxHistoryCountErrorTransformer( private val userWallet: UserWallet, private val error: TxHistoryStateError, private val pendingTransactions: Set, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWallet.walletId) { private val txHistoryItemConverter by lazy { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt similarity index 91% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt index 74fa8ea8a3..f61b4961fe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryCountTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountTransformer.kt @@ -1,11 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import androidx.paging.PagingData import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import timber.log.Timber @@ -13,7 +13,7 @@ import timber.log.Timber internal class SetTxHistoryCountTransformer( userWalletId: UserWalletId, private val transactionsCount: Int, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWalletId) { override fun transform(prevState: WalletState): WalletState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt similarity index 86% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsErrorTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt index 204b038dd8..5eda4b196e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsErrorTransformer.kt @@ -1,16 +1,16 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryListError import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import timber.log.Timber internal class SetTxHistoryItemsErrorTransformer( userWalletId: UserWalletId, private val error: TxHistoryListError, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWalletId) { override fun transform(prevState: WalletState): WalletState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt similarity index 73% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt index 9a47b46770..09c90b2841 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetTxHistoryItemsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryItemsTransformer.kt @@ -1,19 +1,19 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import androidx.paging.PagingData +import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TxHistoryItemFlowConverter -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemFlowConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.flow.Flow import timber.log.Timber internal class SetTxHistoryItemsTransformer( - private val userWallet: UserWallet, - private val flow: Flow>, - private val clickIntents: WalletClickIntentsV2, + userWallet: UserWallet, + private val flow: Flow>, + private val clickIntents: WalletClickIntents, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { @@ -39,7 +39,6 @@ internal class SetTxHistoryItemsTransformer( private fun TxHistoryState.toContentState(): TxHistoryState { val converter = TxHistoryItemFlowConverter( - userWallet = userWallet, currentState = this, clickIntents = clickIntents, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetWarningsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetWarningsTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt index 1fcac2e3d4..7d7246a01e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/SetWarningsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetWarningsTransformer.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import kotlinx.collections.immutable.ImmutableList import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UnlockWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UnlockWalletTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt index c905844bb0..f22dedbc22 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UnlockWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt @@ -1,18 +1,18 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.toImmutableList import timber.log.Timber internal class UnlockWalletTransformer( private val unlockedWallets: List, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateBalanceHidingModeTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateBalanceHidingModeTransformer.kt similarity index 63% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateBalanceHidingModeTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateBalanceHidingModeTransformer.kt index 73be934720..e866e02864 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateBalanceHidingModeTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateBalanceHidingModeTransformer.kt @@ -1,6 +1,6 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState internal class UpdateBalanceHidingModeTransformer( private val isHidingMode: Boolean, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateWalletCardsCountTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt similarity index 88% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateWalletCardsCountTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt index f88cebf21d..1994185f73 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/UpdateWalletCardsCountTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateWalletCardsCountTransformer.kt @@ -1,11 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import timber.log.Timber internal class UpdateWalletCardsCountTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletScreenStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletScreenStateTransformer.kt new file mode 100644 index 0000000000..7bfc34bd81 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletScreenStateTransformer.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState + +internal interface WalletScreenStateTransformer { + + fun transform(prevState: WalletScreenState): WalletScreenState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletStateTransformer.kt similarity index 81% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletStateTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletStateTransformer.kt index a96c554e7e..f02bd16531 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/WalletStateTransformer.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers +package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import kotlinx.collections.immutable.toImmutableList import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt similarity index 70% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt index 2a6139aa60..f43ec73629 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt @@ -1,12 +1,12 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.converter.Converter import java.math.BigDecimal @@ -15,8 +15,13 @@ internal class BalancesAndLimitsBottomSheetConverter( ) : Converter { override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig { + fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount( + amount, + cryptoCurrency = value.symbol, + decimals = value.decimals, + ) + return BalancesAndLimitsBottomSheetConfig( - currency = value.symbol, balance = BalancesAndLimitsBottomSheetConfig.Balance( totalBalance = value.balances.total.let(::formatAmount), availableBalance = value.balances.available.let(::formatAmount), @@ -24,24 +29,18 @@ internal class BalancesAndLimitsBottomSheetConverter( debit = value.balances.debt.let(::formatAmount), pending = value.balances.pendingRefund.let(::formatAmount), amlVerified = value.balances.verified.let(::formatAmount), + onInfoClick = this::showBalanceInfo, ), limit = BalancesAndLimitsBottomSheetConfig.Limit( availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate), inStore = value.limits.remainingOtp.let(::formatAmount), other = value.limits.remainingNoOtp.let(::formatAmount), singleTransaction = value.limits.singleTransaction.let(::formatAmount), + onInfoClick = this::showLimitInfo, ), - onBalanceInfoClick = this::showBalanceInfo, - onLimitInfoClick = this::showLimitInfo, ) } - private fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount( - amount, - cryptoCurrency = "", - decimals = 2, - ) - private fun showBalanceInfo() { eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo)) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCardStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt similarity index 93% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCardStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt index 8c3b84bf05..1e1d944584 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCardStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCardStateConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency @@ -6,7 +6,7 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.utils.converter.Converter internal class MultiWalletCardStateConverter( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt similarity index 96% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCurrencyActionsConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index e43e0fb2a6..16876a9396 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference @@ -7,7 +7,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletCurrencyActionsClickIntentsImplementor import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletCardStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt similarity index 94% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletCardStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt index e54cf09dfa..028d2ea966 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletCardStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency @@ -6,7 +6,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.utils.converter.Converter internal class SingleWalletCardStateConverter( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletMarketPriceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt similarity index 97% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletMarketPriceConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt index 1f1237f976..60fcaa1c3c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/SingleWalletMarketPriceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt similarity index 94% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenItemStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt index e3b9517efb..77aa101de4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.marketprice.PriceChangeType @@ -6,14 +6,14 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import java.math.BigDecimal internal class TokenItemStateConverter( private val appCurrencyProvider: Provider, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : Converter { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -54,7 +54,7 @@ internal class TokenItemStateConverter( ), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()), cryptoPriceState = getCryptoPriceState(), - onItemClick = { clickIntents.onTokenItemClick(currency) }, + onItemClick = { clickIntents.onTokenItemClick(this) }, onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) } @@ -76,7 +76,7 @@ internal class TokenItemStateConverter( id = currency.id.value, iconState = iconStateConverter.convert(value = this), titleState = TokenItemState.TitleState.Content(text = currency.name), - onItemClick = { clickIntents.onTokenItemClick(currency) }, + onItemClick = { clickIntents.onTokenItemClick(this) }, onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt similarity index 87% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenListStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 77b6288d67..02c8d42705 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.appcurrency.model.AppCurrency @@ -7,21 +7,21 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState.TokensListItemState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig internal class TokenListStateConverter( private val tokenList: TokenList, private val selectedWallet: UserWallet, private val appCurrency: AppCurrency, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : Converter { private val tokenStatusConverter = TokenItemStateConverter( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt similarity index 56% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemFlowConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt index 6c642647c3..62c49d5586 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemFlowConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt @@ -1,15 +1,11 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import androidx.paging.* import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState import com.tangem.core.ui.utils.toDateFormat -import com.tangem.core.ui.utils.toTimeFormat -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -19,21 +15,11 @@ import java.util.UUID private val scope = CoroutineScope(Dispatchers.IO) internal class TxHistoryItemFlowConverter( - private val userWallet: UserWallet, private val currentState: TxHistoryState, - private val clickIntents: WalletClickIntentsV2, -) : Converter>, TxHistoryState?> { + private val clickIntents: WalletClickIntents, +) : Converter>, TxHistoryState?> { - private val txHistoryItemConverter by lazy { - val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() - TxHistoryItemStateConverter( - symbol = blockchain.currency, - decimals = blockchain.decimals(), - clickIntents = clickIntents, - ) - } - - override fun convert(value: Flow>): TxHistoryState { + override fun convert(value: Flow>): TxHistoryState { val txHistoryContent = currentState as? TxHistoryState.Content ?: TxHistoryState.Content(contentItems = MutableStateFlow(PagingData.empty())) @@ -43,16 +29,14 @@ internal class TxHistoryItemFlowConverter( .onEach { txHistoryStatePagingData -> txHistoryContent.contentItems.update { txHistoryStatePagingData - .map { item -> - // [createTransactionState] returns timestamp without formatting - TxHistoryItemState.Transaction(state = createTransactionState(item)) + .map { item -> + TxHistoryItemState.Transaction(item) } .insertHeaderItem( terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE, item = TxHistoryItemState.Title(clickIntents::onExploreClick), ) - .insertGroupTitle() // method uses the raw timestamp - .formatTransactionsTimestamp() // method formats the timestamp + .insertGroupTitle() } } .cachedIn(scope) @@ -61,10 +45,6 @@ internal class TxHistoryItemFlowConverter( return txHistoryContent } - private fun createTransactionState(item: TxHistoryItem): TransactionState { - return txHistoryItemConverter.convert(value = item) - } - private fun PagingData.insertGroupTitle(): PagingData { return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after -> // Use raw timestamp to get date @@ -88,28 +68,11 @@ internal class TxHistoryItemFlowConverter( } } - /** - * Map the [PagingData] to format the [TxHistoryItemState] timestamp - */ - private fun PagingData.formatTransactionsTimestamp(): PagingData { - return map { txHistoryItemState -> - if (txHistoryItemState is TxHistoryItemState.Transaction && - txHistoryItemState.state is TransactionState.Content - ) { - val txContent = txHistoryItemState.state as TransactionState.Content - txHistoryItemState.copy( - state = txContent.copy(timestamp = txContent.timestamp.toLong().toTimeFormat()), - ) - } else { - txHistoryItemState - } - } - } - private fun TxHistoryItemState?.getTimestamp(): Long? { return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) { val txContent = this.state as TransactionState.Content - requireNotNull(txContent.timestamp.toLongOrNull()) { "Timestamp must be Long type" } + + txContent.timestamp } else { null } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt similarity index 89% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index e9c78803f3..d3fd7a6127 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -1,13 +1,14 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat import com.tangem.utils.toFormattedCurrencyString @@ -15,7 +16,7 @@ import com.tangem.utils.toFormattedCurrencyString internal class TxHistoryItemStateConverter( private val symbol: String, private val decimals: Int, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : Converter { override fun convert(value: TxHistoryItem): TransactionState { @@ -27,13 +28,14 @@ internal class TxHistoryItemStateConverter( return TransactionState.Content( txHash = item.txHash, amount = item.getAmount(), - timestamp = item.getRawTimestamp(), + time = item.timestampInMillis.toTimeFormat(), status = item.status.tiUiStatus(), direction = item.extractDirection(), iconRes = item.extractIcon(), title = item.extractTitle(), subtitle = item.extractSubtitle(), - onClick = { clickIntents.onTransactionClick(item.txHash) }, + timestamp = item.timestampInMillis, + onClick = { clickIntents.onVisaTransactionClick(item.txHash) }, ) } @@ -85,14 +87,6 @@ internal class TxHistoryItemStateConverter( private fun TxHistoryItem.extractDirection() = if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING - /** - * Get timestamp without formatting. - * It's life hack that help us to add transaction's group title to flow. - * - * @see [convert] - */ - private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString() - private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt new file mode 100644 index 0000000000..f431a74a5e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt @@ -0,0 +1,86 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toImmutableList +import org.joda.time.DateTimeZone +import java.math.BigDecimal +import java.util.Currency + +internal class VisaTxDetailsBottomSheetConverter( + private val visaCurrency: VisaCurrency, + private val clickIntents: VisaWalletIntents, +) : Converter { + + override fun convert(value: VisaTxDetails): VisaTxDetailsBottomSheetConfig { + return VisaTxDetailsBottomSheetConfig( + transaction = createTransaction(value), + requests = value.requests.map(::createRequest).toImmutableList(), + ) + } + + private fun createTransaction(details: VisaTxDetails): VisaTxDetailsBottomSheetConfig.Transaction { + return VisaTxDetailsBottomSheetConfig.Transaction( + id = details.id, + type = details.type, + status = details.status, + blockchainAmount = formatNetworkAmount(details.blockchainAmount), + blockchainFee = formatNetworkAmount(details.blockchainFee), + transactionAmount = formatFiatAmount(details.transactionAmount, details.fiatCurrency), + transactionCurrencyCode = details.transactionCurrencyCode.toString(), + merchantName = details.merchantName ?: UNKNOWN, + merchantCity = details.merchantCity ?: UNKNOWN, + merchantCountryCode = details.merchantCountryCode ?: UNKNOWN, + merchantCategoryCode = details.merchantCategoryCode ?: UNKNOWN, + ) + } + + private fun createRequest(request: VisaTxDetails.Request): VisaTxDetailsBottomSheetConfig.Request { + val localDate = request.requestDate.withZone(DateTimeZone.getDefault()) + val exploreUrl = request.exploreUrl + + return VisaTxDetailsBottomSheetConfig.Request( + id = request.id, + type = request.requestType, + status = request.requestStatus, + blockchainAmount = formatNetworkAmount(request.blockchainAmount), + blockchainFee = formatNetworkAmount(request.blockchainFee), + transactionAmount = formatFiatAmount(request.transactionAmount, request.fiatCurrency), + currencyCode = request.billingCurrencyCode.toString(), + errorCode = request.errorCode, + date = DateTimeFormatters.formatDate(DateTimeFormatters.dateTimeFormatter, date = localDate), + txHash = request.txHash ?: UNKNOWN, + txStatus = request.txStatus ?: UNKNOWN, + onExploreClick = if (exploreUrl != null) { + { clickIntents.onExploreClick(exploreUrl) } + } else { + null + }, + ) + } + + private fun formatNetworkAmount(amount: BigDecimal): String { + return BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = amount, + cryptoCurrency = visaCurrency.symbol, + decimals = visaCurrency.decimals, + ) + } + + private fun formatFiatAmount(amount: BigDecimal, fiatCurrency: Currency): String { + return BigDecimalFormatter.formatFiatAmount( + fiatAmount = amount, + fiatCurrencyCode = fiatCurrency.currencyCode, + fiatCurrencySymbol = fiatCurrency.symbol, + ) + } + + private companion object { + const val UNKNOWN = "Unknown" + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt new file mode 100644 index 0000000000..bddc7ec7aa --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt @@ -0,0 +1,46 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.core.ui.components.transactions.state.TransactionState +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents +import com.tangem.utils.converter.Converter +import org.joda.time.DateTimeZone + +internal class VisaTxHistoryItemStateConverter( + private val visaCurrency: VisaCurrency, + private val clickIntents: VisaWalletIntents, +) : Converter { + + override fun convert(value: VisaTxHistoryItem): TransactionState { + val localDate = value.date.withZone(DateTimeZone.getDefault()) + val time = DateTimeFormatters.formatTime(time = localDate) + val subtitle = "$time • ${value.status}" + + return TransactionState.Content( + txHash = value.id, + amount = BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = value.amount, + cryptoCurrency = visaCurrency.symbol, + decimals = visaCurrency.decimals, + ), + // Show tx fiat amount instead of tx time + time = BigDecimalFormatter.formatFiatAmount( + fiatAmount = value.fiatAmount, + fiatCurrencyCode = value.fiatCurrency.currencyCode, + fiatCurrencySymbol = value.fiatCurrency.symbol, + ), + status = TransactionState.Content.Status.Confirmed, + direction = TransactionState.Content.Direction.INCOMING, + iconRes = R.drawable.ic_arrow_up_24, + title = stringReference(value = value.merchantName ?: "Unknown merchant"), + subtitle = stringReference(subtitle), + timestamp = localDate.millis, + onClick = { clickIntents.onVisaTransactionClick(value.id) }, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/UserWalletConverterExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt similarity index 83% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/UserWalletConverterExt.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt index 9d03b341a7..c7a8830b71 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/UserWalletConverterExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.utils +package com.tangem.feature.wallet.presentation.wallet.state.utils import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState internal inline fun UserWallet.createStateByWalletType( multiCurrencyCreator: () -> WalletState.MultiCurrency, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt similarity index 72% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletEventSender.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt index 7b225aa521..34859d54d9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.utils +package com.tangem.feature.wallet.presentation.wallet.state.utils import com.tangem.core.ui.event.consumedEvent -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SendEventTransformer +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SendEventTransformer import javax.inject.Inject /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt similarity index 90% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletLoadingStateFactory.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index b9654a68e2..866f9981a0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.utils +package com.tangem.feature.wallet.presentation.wallet.state.utils import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TxHistoryState @@ -6,11 +6,8 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.model.* +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow @@ -20,7 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow * * @property clickIntents click intents */ -internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIntentsV2) { +internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIntents) { fun create(userWallet: UserWallet): WalletState { return userWallet.createStateByWalletType( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletScreenStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletScreenStateTransformer.kt deleted file mode 100644 index 9cae2fd8e8..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state2/transformers/WalletScreenStateTransformer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state2.transformers - -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState - -internal interface WalletScreenStateTransformer { - - fun transform(prevState: WalletScreenState): WalletScreenState -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 379657ed45..d9cc56d227 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -9,15 +9,16 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach +import timber.log.Timber internal typealias MaybeTokenListFlow = Flow> @@ -25,7 +26,7 @@ internal typealias MaybeTokenListFlow = Flow> internal abstract class BasicTokenListSubscriber( private val userWallet: UserWallet, private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, @@ -33,35 +34,48 @@ internal abstract class BasicTokenListSubscriber( protected abstract fun tokenListFlow(): MaybeTokenListFlow + protected open suspend fun onTokenListReceived(tokenList: TokenList) { /* no-op */ + } + override fun create(coroutineScope: CoroutineScope): Flow<*> { return combine( flow = tokenListFlow() - .onEach { - val displayedState = stateHolder.getWalletIfSelected(userWallet.walletId) + .onEach { maybeTokenList -> + val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId) - tokenListAnalyticsSender.send(displayedState, userWallet, it.getOrElse { return@onEach }) + tokenListAnalyticsSender.send( + displayedUiState = displayedState, + userWallet = userWallet, + tokenList = maybeTokenList.getOrElse { return@onEach }, + ) } .distinctUntilChanged(), flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> - updateContent(maybeTokenList, maybeAppCurrency.getOrElse { AppCurrency.Default }) - walletWithFundsChecker.check(maybeTokenList) + val tokenList = maybeTokenList.getOrElse { e -> + Timber.e("Failed to load token list: $e") + SetTokenListErrorTransformer(userWallet.walletId, e) + return@combine + } + val appCurrency = maybeAppCurrency.getOrElse { e -> + Timber.e("Failed to load app currency: $e") + AppCurrency.Default + } + + updateContent(tokenList, appCurrency) + walletWithFundsChecker.check(tokenList) + onTokenListReceived(tokenList) }, ) } - private fun updateContent(maybeTokenList: Either, appCurrency: AppCurrency) { + private fun updateContent(tokenList: TokenList, appCurrency: AppCurrency) { stateHolder.update( - maybeTokenList.fold( - ifLeft = { SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it) }, - ifRight = { - SetTokenListTransformer( - tokenList = it, - userWallet = userWallet, - appCurrency = appCurrency, - clickIntents = clickIntents, - ) - }, + SetTokenListTransformer( + tokenList = tokenList, + userWallet = userWallet, + appCurrency = appCurrency, + clickIntents = clickIntents, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 46de409a7d..8eb0af9ab2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -1,19 +1,22 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @Suppress("LongParameterList") internal class MultiWalletTokenListSubscriber( private val userWallet: UserWallet, private val getTokenListUseCase: GetTokenListUseCase, + private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, stateHolder: WalletStateController, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, @@ -27,4 +30,29 @@ internal class MultiWalletTokenListSubscriber( ) { override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) + + override suspend fun onTokenListReceived(tokenList: TokenList) { + updateSortingIfNeeded(tokenList) + } + + private suspend fun updateSortingIfNeeded(tokenList: TokenList) { + if (tokenList.totalFiatBalance is TokenList.FiatBalance.Loading || + tokenList.sortedBy == TokenList.SortType.NONE + ) { + return + } + + applyTokenListSortingUseCase( + userWalletId = userWallet.walletId, + sortedTokensIds = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { group -> + group.currencies.map { it.currency.id } + } + is TokenList.Ungrouped -> tokenList.currencies.map { it.currency.id } + is TokenList.Empty -> return + }, + isGroupedByNetwork = tokenList is TokenList.GroupedByNetwork, + isSortedByBalance = true, + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt index 09a4cc324d..d9029393c1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt @@ -3,10 +3,10 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetWarningsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.onEach internal class MultiWalletWarningsSubscriber( private val userWalletId: UserWalletId, private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) : WalletSubscriber() { @@ -27,7 +27,7 @@ internal class MultiWalletWarningsSubscriber( .conflate() .distinctUntilChanged() .onEach { warnings -> - val displayedState = stateHolder.getWalletIfSelected(userWalletId) + val displayedState = stateHolder.getWalletState(userWalletId) stateHolder.update(SetWarningsTransformer(userWalletId, warnings)) walletWarningsAnalyticsSender.send(displayedState, warnings) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt index de0c3f2634..41fef9b424 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt @@ -13,8 +13,8 @@ import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetPrimaryCurrencyTransformer +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetPrimaryCurrencyTransformer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt index 21664ba8bd..4751fed097 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt @@ -5,16 +5,16 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetCryptoCurrencyActionsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetCryptoCurrencyActionsTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* internal class SingleWalletButtonsSubscriber( private val userWallet: UserWallet, private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, ) : WalletSubscriber() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt index e4b6e2f5e9..f266b681fd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt @@ -3,10 +3,10 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetWarningsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -22,7 +22,7 @@ internal class SingleWalletNotificationsSubscriber( private val stateHolder: WalletStateController, private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow> { @@ -30,7 +30,7 @@ internal class SingleWalletNotificationsSubscriber( .conflate() .distinctUntilChanged() .onEach { warnings -> - val displayedState = stateHolder.getWalletIfSelected(userWalletId) + val displayedState = stateHolder.getWalletState(userWalletId) stateHolder.update(SetWarningsTransformer(userWalletId, warnings)) walletWarningsAnalyticsSender.send(displayedState, warnings) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 3fe184f524..54c8ca07de 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -5,15 +5,15 @@ import com.tangem.domain.tokens.GetCardTokensListUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @Suppress("LongParameterList") internal class SingleWalletWithTokenListSubscriber( private val userWallet: UserWallet, private val getCardTokensListUseCase: GetCardTokensListUseCase, stateHolder: WalletStateController, - clickIntents: WalletClickIntentsV2, + clickIntents: WalletClickIntents, tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt index d9d435e6f0..7561ea68c5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt @@ -2,8 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import androidx.paging.PagingData import androidx.paging.cachedIn +import androidx.paging.map import arrow.core.Either -import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -14,13 +14,17 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.* -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountErrorTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map typealias MaybeTxHistoryCount = Either typealias MaybeTxHistoryItems = Either>> @@ -30,31 +34,13 @@ internal class TxHistorySubscriber( private val userWallet: UserWallet, private val isRefresh: Boolean, private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, + private val clickIntents: WalletClickIntents, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow> { - // TODO: [REDACTED_JIRA] - if (userWallet.scanResponse.cardTypesResolver.isVisaWallet()) { - return flow { - stateHolder.update( - object : WalletStateTransformer(userWallet.walletId) { - override fun transform(prevState: WalletState): WalletState { - return when (prevState) { - is WalletState.Visa.Content -> prevState.copy( - txHistoryState = TxHistoryState.Empty(onExploreClick = {}), - ) - else -> prevState - } - } - }, - ) - } - } - return flow { getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status -> val maybeTxHistoryItemCount = txHistoryItemsCountUseCase( @@ -109,10 +95,19 @@ internal class TxHistorySubscriber( clickIntents = clickIntents, ) }, - ifRight = { + ifRight = { itemsFlow -> + val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() + val itemConverter = TxHistoryItemStateConverter( + symbol = blockchain.currency, + decimals = blockchain.decimals(), + clickIntents = clickIntents, + ) + SetTxHistoryItemsTransformer( userWallet = userWallet, - flow = it, + flow = itemsFlow.map { items -> + items.map(itemConverter::convert) + }, clickIntents = clickIntents, ) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletBalancesAndLimitsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletBalancesAndLimitsSubscriber.kt deleted file mode 100644 index a25af15cdc..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletBalancesAndLimitsSubscriber.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.subscribers - -import com.tangem.domain.visa.GetVisaCurrencyUseCase -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetBalancesAndLimitsTransformer -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -@Suppress("LongParameterList") -internal class VisaWalletBalancesAndLimitsSubscriber( - private val userWallet: UserWallet, - private val stateHolder: WalletStateController, - private val isRefresh: Boolean, - private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, - private val clickIntents: WalletClickIntentsV2, -) : WalletSubscriber() { - - override fun create(coroutineScope: CoroutineScope): Flow<*> { - return flow { - stateHolder.update( - SetBalancesAndLimitsTransformer( - userWallet = userWallet, - maybeVisaCurrency = getVisaCurrencyUseCase(userWallet.walletId, isRefresh), - clickIntents = clickIntents, - ), - ) - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt new file mode 100644 index 0000000000..552dbba1a5 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/VisaWalletSubscriber.kt @@ -0,0 +1,105 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map +import arrow.core.Either +import arrow.core.getOrElse +import com.tangem.domain.txhistory.models.TxHistoryListError +import com.tangem.domain.visa.GetVisaCurrencyUseCase +import com.tangem.domain.visa.GetVisaTxHistoryUseCase +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetBalancesAndLimitsTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxHistoryItemStateConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import timber.log.Timber + +internal class VisaWalletSubscriber( + private val userWallet: UserWallet, + private val stateController: WalletStateController, + private val isRefresh: Boolean, + private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, + private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase, + private val clickIntents: WalletClickIntents, +) : WalletSubscriber() { + + override fun create(coroutineScope: CoroutineScope): Flow<*> { + return flow { + setLoadingTxHistoryState() + + val maybeCurrency = getVisaCurrencyUseCase(userWallet.walletId, isRefresh) + setLoadedCurrencyState(maybeCurrency) + + val currency = maybeCurrency.getOrElse { + setFailedTxHistoryState(it) + return@flow + } + val txHistoryItemsFlow = getVisaTxHistoryUseCase(userWallet.walletId, isRefresh = isRefresh) + .map { maybeTxHistoryItems -> + maybeTxHistoryItems.getOrElse { + Timber.e(it, "Failed to load tx history for wallet ${userWallet.walletId}") + throw it + } + } + .catch { setFailedTxHistoryState(it) } + .cachedIn(coroutineScope) + + setLoadedTxHistoryState(txHistoryItemsFlow, currency) + } + } + + private fun setLoadedCurrencyState(maybeCurrency: Either) { + stateController.update( + SetBalancesAndLimitsTransformer( + userWallet = userWallet, + maybeVisaCurrency = maybeCurrency, + clickIntents = clickIntents, + ), + ) + } + + private fun setLoadingTxHistoryState() { + stateController.update( + SetTxHistoryCountTransformer( + userWalletId = userWallet.walletId, + transactionsCount = 10, + clickIntents = clickIntents, + ), + ) + } + + private fun setFailedTxHistoryState(it: Throwable) { + stateController.update( + SetTxHistoryItemsErrorTransformer( + userWalletId = userWallet.walletId, + error = TxHistoryListError.DataError(it), + clickIntents = clickIntents, + ), + ) + } + + private fun setLoadedTxHistoryState(itemsFlow: Flow>, currency: VisaCurrency) { + val itemConverter = VisaTxHistoryItemStateConverter(currency, clickIntents) + + stateController.update( + SetTxHistoryItemsTransformer( + userWallet = userWallet, + flow = itemsFlow.map { items -> + items.map(itemConverter::convert) + }, + clickIntents = clickIntents, + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt index ab5bb8bff6..c5303e085e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt @@ -9,7 +9,7 @@ import com.tangem.core.ui.components.DialogButton import com.tangem.core.ui.components.TextInputDialog import com.tangem.core.ui.extensions.resolveReference import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState @Composable internal fun WalletAlert(state: WalletAlertState, onDismiss: () -> Unit) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index dbb5f28cfa..cce277150d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -1,34 +1,33 @@ package com.tangem.feature.wallet.presentation.wallet.ui -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import android.widget.Toast import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString -import com.google.android.play.core.review.ReviewInfo -import com.google.android.play.core.review.ReviewManager -import com.google.android.play.core.review.ReviewManagerFactory -import com.google.android.play.core.tasks.Task import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import timber.log.Timber +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester +import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex +import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling +@Suppress("LongParameterList") @Composable internal fun WalletEventEffect( walletsListState: LazyListState, snackbarHostState: SnackbarHostState, event: StateEvent, + selectedWalletIndex: Int, onAutoScrollSet: () -> Unit, onAlertConfigSet: (WalletAlertState) -> Unit, ) { + val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val resources = LocalContext.current.resources val clipboardManager = LocalClipboardManager.current @@ -38,7 +37,7 @@ internal fun WalletEventEffect( when (value) { is WalletEvent.ChangeWallet -> { onAutoScrollSet() - walletsListState.animateScrollToItem(index = value.index) + walletsListState.animateScrollByIndex(prevIndex = selectedWalletIndex, newIndex = value.index) } is WalletEvent.ShowError -> { snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) @@ -52,49 +51,12 @@ internal fun WalletEventEffect( } is WalletEvent.ShowAlert -> onAlertConfigSet(value.state) is WalletEvent.RateApp -> { - val reviewManager = ReviewManagerFactory.create(context) - val requestTask = reviewManager.requestReviewFlow() - - requestTask - .addOnCompleteListener { - handleOnCompleteRequestTask( - reviewManager = reviewManager, - activity = context.findActivity(), - task = it, - onDismissClick = value.onDismissClick, - ) - } - .addOnFailureListener(Timber::e) + ReviewManagerRequester.request(context = context, onDismissClick = value.onDismissClick) + } + is WalletEvent.DemonstrateWalletsScrollPreview -> { + walletsListState.demonstrateScrolling(coroutineScope = coroutineScope, direction = value.direction) } - is WalletEvent.DemonstrateWalletsScrollPreview -> Unit } }, ) -} - -private fun Context.findActivity(): Activity { - var context = this - while (context is ContextWrapper) { - if (context is Activity) return context - context = context.baseContext - } - error("Permissions should be called in the context of an Activity") -} - -private fun handleOnCompleteRequestTask( - reviewManager: ReviewManager, - activity: Activity, - task: Task, - onDismissClick: () -> Unit, -) { - if (task.isSuccessful) { - val reviewFlow = reviewManager.launchReviewFlow(activity, task.result) - reviewFlow - .addOnCompleteListener { resultReviewTask -> - if (!resultReviewTask.isSuccessful) onDismissClick() - } - .addOnFailureListener(Timber::e) - } else { - Timber.e(task.exception) - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffectV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffectV2.kt deleted file mode 100644 index 73d54f4ddc..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffectV2.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui - -import android.widget.Toast -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString -import com.tangem.core.ui.event.EventEffect -import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester -import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex -import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling - -@Suppress("LongParameterList") -@Composable -internal fun WalletEventEffectV2( - walletsListState: LazyListState, - snackbarHostState: SnackbarHostState, - event: StateEvent, - selectedWalletIndex: Int, - onAutoScrollSet: () -> Unit, - onAlertConfigSet: (WalletAlertState) -> Unit, -) { - val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current - val resources = LocalContext.current.resources - val clipboardManager = LocalClipboardManager.current - EventEffect( - event = event, - onTrigger = { value -> - when (value) { - is WalletEvent.ChangeWallet -> { - onAutoScrollSet() - walletsListState.animateScrollByIndex(prevIndex = selectedWalletIndex, newIndex = value.index) - } - is WalletEvent.ShowError -> { - snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) - } - is WalletEvent.ShowToast -> { - Toast.makeText(context, value.text.resolveReference(resources), Toast.LENGTH_SHORT).show() - } - is WalletEvent.CopyAddress -> { - clipboardManager.setText(AnnotatedString(value.address)) - Toast.makeText(context, value.toast.resolveReference(resources), Toast.LENGTH_SHORT).show() - } - is WalletEvent.ShowAlert -> onAlertConfigSet(value.state) - is WalletEvent.RateApp -> { - ReviewManagerRequester.request(context = context, onDismissClick = value.onDismissClick) - } - is WalletEvent.DemonstrateWalletsScrollPreview -> { - walletsListState.demonstrateScrolling(coroutineScope = coroutineScope, direction = value.direction) - } - } - }, - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index cd3312809b..8341a0792c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope @@ -9,269 +10,387 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.FabPosition -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.Dp import androidx.paging.compose.collectAsLazyPagingItems +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.components.atoms.handComposableComponentHeight import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig +import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.* -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState +import com.tangem.feature.wallet.presentation.wallet.state.model.* +import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.common.* import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock +import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet +import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet +import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock +import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch -/** - * Wallet screen - * - * @param state screen state - * -[REDACTED_AUTHOR] - */ @Composable -internal fun WalletScreen(state: WalletState) { +internal fun WalletScreen( + state: WalletScreenState, + bottomSheetHeaderHeightProvider: () -> Dp, + bottomSheetContent: @Composable () -> Unit, +) { BackHandler(onBack = state.onBackClick) - when (state) { - is WalletState.ContentState -> { - val walletsListState = rememberLazyListState( - initialFirstVisibleItemIndex = state.walletsListConfig.selectedWalletIndex, - ) - val snackbarHostState = remember { SnackbarHostState() } - val isAutoScroll = remember { mutableStateOf(value = false) } + // It means that screen is still initializing + if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return - WalletContent( - state = state, - walletsListState = walletsListState, - snackbarHostState = snackbarHostState, - isAutoScroll = isAutoScroll, - onAutoScrollReset = { isAutoScroll.value = false }, - ) + val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex) + val snackbarHostState = remember(::SnackbarHostState) + val isAutoScroll = remember { mutableStateOf(value = false) } - var alertConfig by remember { mutableStateOf(value = null) } - - WalletEventEffect( - walletsListState = walletsListState, - snackbarHostState = snackbarHostState, - event = state.event, - onAutoScrollSet = { isAutoScroll.value = true }, - onAlertConfigSet = { alertConfig = it }, - ) - - alertConfig?.let { - WalletAlert(state = it, onDismiss = { alertConfig = null }) - } - } - is WalletState.Initial -> Unit - } -} - -@Composable -private fun WalletContent( - state: WalletState.ContentState, - walletsListState: LazyListState, - snackbarHostState: SnackbarHostState, - isAutoScroll: State, - onAutoScrollReset: () -> Unit, -) { - BaseScaffold(state = state, snackbarHostState) { scaffoldPaddings -> - val movableItemModifier = Modifier.changeWalletAnimator(walletsListState) - - UpdatableContainer( - pullToRefreshConfig = state.pullToRefreshConfig, - modifier = Modifier.padding(paddingValues = scaffoldPaddings), - ) { - val txHistoryItems = if (state is WalletSingleCurrencyState && - state.txHistoryState is TxHistoryState.Content - ) { - (state.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems() - } else { - null - } - - val betweenItemsPadding = TangemTheme.dimens.spacing14 - val horizontalPadding = TangemTheme.dimens.spacing16 - val itemModifier = movableItemModifier - .padding(top = betweenItemsPadding) - .padding(horizontal = horizontalPadding) - - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues( - top = TangemTheme.dimens.spacing8, - bottom = TangemTheme.dimens.spacing92, - ), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - item { - WalletsList( - config = state.walletsListConfig, - lazyListState = walletsListState, - isBalanceHidden = state.isBalanceHidden, - ) - } - - if (state is WalletSingleCurrencyState) { - controlButtons( - configs = state.buttons, - selectedWalletIndex = state.walletsListConfig.selectedWalletIndex, - modifier = movableItemModifier.padding(top = betweenItemsPadding), - ) - } - - notifications(configs = state.notifications, modifier = itemModifier) - - if (state is WalletSingleCurrencyState.Content) { - marketPriceBlock(state = state.marketPriceBlockState, modifier = itemModifier) - } - - contentItems( - state = state, - txHistoryItems = txHistoryItems, - isBalanceHidden = state.isBalanceHidden, - modifier = movableItemModifier, - ) - - organizeTokens(state = state, itemModifier = itemModifier) - } - } - } - - WalletBottomSheets(state = state) - - WalletsListEffects( - lazyListState = walletsListState, - walletsListConfig = state.walletsListConfig, + WalletContent( + state = state, + walletsListState = walletsListState, + snackbarHostState = snackbarHostState, isAutoScroll = isAutoScroll, - onAutoScrollReset = onAutoScrollReset, + onAutoScrollReset = { isAutoScroll.value = false }, + bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, + bottomSheetContent = bottomSheetContent, + ) + + var alertConfig by remember { mutableStateOf(value = null) } + + alertConfig?.let { + WalletAlert(state = it, onDismiss = { alertConfig = null }) + } + + WalletEventEffect( + event = state.event, + selectedWalletIndex = state.selectedWalletIndex, + walletsListState = walletsListState, + snackbarHostState = snackbarHostState, + onAlertConfigSet = { alertConfig = it }, + onAutoScrollSet = { isAutoScroll.value = true }, ) } -internal fun LazyListScope.organizeTokens(state: WalletState.ContentState, itemModifier: Modifier) { - if (state is WalletMultiCurrencyState) { - val contentTokenListState = state.tokensListState as? WalletTokensListState.ContentState - val organizeTokensButton = contentTokenListState?.organizeTokensButton +@Suppress("LongMethod", "LongParameterList") +@Composable +private fun WalletContent( + state: WalletScreenState, + walletsListState: LazyListState, + snackbarHostState: SnackbarHostState, + isAutoScroll: State, + bottomSheetHeaderHeightProvider: () -> Dp, + onAutoScrollReset: () -> Unit, + bottomSheetContent: @Composable () -> Unit, +) { + var selectedWalletIndex by remember { mutableIntStateOf(state.selectedWalletIndex) } + val selectedWallet = state.wallets[selectedWalletIndex] - if (organizeTokensButton is OrganizeTokensButtonState.Visible) { - organizeTokensButton( - modifier = itemModifier, - isEnabled = organizeTokensButton.isEnabled, - onClick = organizeTokensButton.onClick, + val scaffoldContent: @Composable () -> Unit = { + val movableItemModifier = Modifier.changeWalletAnimator(walletsListState) + + val lazyTxHistoryItems = (selectedWallet as? TxHistoryStateHolder)?.let { walletState -> + (walletState.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems() + } + + val txHistoryItems by remember(selectedWallet.walletCardState.id, lazyTxHistoryItems?.itemCount) { + mutableStateOf(value = lazyTxHistoryItems) + } + + val betweenItemsPadding = TangemTheme.dimens.spacing14 + val horizontalPadding = TangemTheme.dimens.spacing16 + val itemModifier = movableItemModifier + .padding(top = betweenItemsPadding) + .padding(horizontal = horizontalPadding) + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues( + top = TangemTheme.dimens.spacing8, + bottom = TangemTheme.dimens.spacing92, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + item( + key = state.wallets.map { it.walletCardState.id }, + contentType = state.wallets.map { it.walletCardState.id }, + ) { + WalletsList( + lazyListState = walletsListState, + wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(), + isBalanceHidden = state.isHidingMode, + ) + } + + (selectedWallet as? WalletState.SingleCurrency)?.let { + controlButtons( + configs = it.buttons, + selectedWalletIndex = selectedWalletIndex, + modifier = movableItemModifier.padding(top = betweenItemsPadding), + ) + } + + notifications(configs = selectedWallet.warnings, modifier = itemModifier) + + (selectedWallet as? WalletState.SingleCurrency)?.let { walletState -> + walletState.marketPriceBlockState?.let { marketPriceBlockState -> + marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) + } + } + + (selectedWallet as? WalletState.Visa.Content)?.let { + depositButton( + modifier = itemModifier.fillMaxWidth(), + state = it.depositButtonState, + ) + + balancesAndLimitsBlock( + modifier = itemModifier, + state = it.balancesAndLimitBlockState, + ) + } + + contentItems( + state = selectedWallet, + txHistoryItems = txHistoryItems, + isBalanceHidden = state.isHidingMode, + modifier = movableItemModifier, ) + + organizeTokens(state = selectedWallet, itemModifier = itemModifier) + } + + val bottomSheetConfig = selectedWallet.bottomSheetConfig + if (bottomSheetConfig != null) { + when (bottomSheetConfig.content) { + is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) + is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) + is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) + is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) + is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) + is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) + } + } + + WalletsListEffects( + lazyListState = walletsListState, + selectedWalletIndex = selectedWalletIndex, + onWalletChange = state.onWalletChange, + onSelectedWalletIndexSet = { selectedWalletIndex = it }, + isAutoScroll = isAutoScroll, + onAutoScrollReset = onAutoScrollReset, + ) + } + + if (state.manageTokenRedesignToggle) { + BaseScaffoldManageTokenRedesign( + state = state, + selectedWallet = selectedWallet, + snackbarHostState = snackbarHostState, + bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, + bottomSheetContent = bottomSheetContent, + ) { + scaffoldContent() + } + } else { + BaseScaffold( + state = state, + selectedWallet = selectedWallet, + snackbarHostState = snackbarHostState, + ) { + scaffoldContent() } } } +@Suppress("LongParameterList", "LongMethod") +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +private fun BaseScaffoldManageTokenRedesign( + state: WalletScreenState, + selectedWallet: WalletState, + snackbarHostState: SnackbarHostState, + bottomSheetHeaderHeightProvider: () -> Dp, + bottomSheetContent: @Composable () -> Unit, + content: @Composable () -> Unit, +) { + val scaffoldState = rememberBottomSheetScaffoldState() + val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } + val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() } + val systemUiController = rememberSystemUiController() + val navigationBarColor = TangemTheme.colors.background.primary + val navigationBarColorWithout = TangemTheme.colors.background.secondary + + DisposableEffect( + navigationBarColor, + navigationBarColorWithout, + ) { + systemUiController.setNavigationBarColor(navigationBarColor) + onDispose { + systemUiController.setNavigationBarColor(navigationBarColorWithout) + } + } + + val keyboardShown by keyboardAsState() + // expand bottom sheet when keyboard appears + LaunchedEffect(keyboardShown is Keyboard.Opened) { + if (keyboardShown is Keyboard.Opened) { + scaffoldState.bottomSheetState.expand() + } + } + + val keyboardController = LocalSoftwareKeyboardController.current + val sheetHasBeenHidden = scaffoldState.bottomSheetState.targetValue == SheetValue.PartiallyExpanded + // hide keyboard when bottom sheet is about to be hidden + LaunchedEffect(sheetHasBeenHidden) { + if (sheetHasBeenHidden) { + keyboardController?.hide() + } + } + + val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight + val coroutineScope = rememberCoroutineScope() + + BottomSheetScaffold( + topBar = { + WalletTopBar(config = state.topBarConfig) + }, + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + containerColor = TangemTheme.colors.background.secondary, + sheetContainerColor = TangemTheme.colors.background.primary, + scaffoldState = scaffoldState, + sheetPeekHeight = peekHeight, + sheetDragHandle = { + Hand(modifier = Modifier.background(color = TangemTheme.colors.background.primary)) + }, + sheetContent = { + BoxWithConstraints { + Box( + modifier = Modifier + .sizeIn(maxHeight = maxHeight - statusBarHeight) + .align(Alignment.BottomCenter), + ) { + bottomSheetContent() + } + } + + // hide bottom sheet when back pressed + BackHandler( + keyboardShown is Keyboard.Closed && + scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded, + ) { + coroutineScope.launch { scaffoldState.bottomSheetState.partialExpand() } + } + }, + content = { paddingValues -> + val pullRefreshState = rememberPullRefreshState( + refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, + onRefresh = selectedWallet.pullToRefreshConfig.onRefresh, + ) + + Box( + modifier = Modifier + .pullRefresh(pullRefreshState) + .padding(paddingValues), + ) { + content() + + WalletPullToRefreshIndicator( + isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) + } + }, + ) +} + @OptIn(ExperimentalMaterialApi::class) -@Composable -private fun UpdatableContainer( - pullToRefreshConfig: WalletPullToRefreshConfig, - modifier: Modifier = Modifier, - content: @Composable BoxScope.() -> Unit, -) { - val pullRefreshState = rememberPullRefreshState( - refreshing = pullToRefreshConfig.isRefreshing, - onRefresh = pullToRefreshConfig.onRefresh, - ) - - Box(modifier = modifier.pullRefresh(pullRefreshState)) { - content() - - WalletPullToRefreshIndicator( - isRefreshing = pullToRefreshConfig.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - } -} - @Composable private fun BaseScaffold( - state: WalletState.ContentState, + state: WalletScreenState, + selectedWallet: WalletState, snackbarHostState: SnackbarHostState, - content: @Composable (PaddingValues) -> Unit, + content: @Composable () -> Unit, ) { Scaffold( topBar = { WalletTopBar(config = state.topBarConfig) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { - if (state is WalletMultiCurrencyState.Content && state.isManageTokensAvailable) { - ManageTokensButton(onManageTokensClick = state.onManageTokensClick) + val manageTokensButtonConfig by remember(state.selectedWalletIndex) { + mutableStateOf( + (state.wallets[state.selectedWalletIndex] as? WalletState.MultiCurrency)?.manageTokensButtonConfig, + ) } + + manageTokensButtonConfig?.let { ManageTokensButton(onClick = it.onClick) } }, floatingActionButtonPosition = FabPosition.Center, containerColor = TangemTheme.colors.background.secondary, - content = content, + content = { + val pullRefreshState = rememberPullRefreshState( + refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, + onRefresh = selectedWallet.pullToRefreshConfig.onRefresh, + ) + + Box( + modifier = Modifier + .pullRefresh(pullRefreshState) + .padding(it), + ) { + content() + + WalletPullToRefreshIndicator( + isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) + } + }, ) } @Composable -private fun ManageTokensButton(onManageTokensClick: () -> Unit) { +private fun ManageTokensButton(onClick: () -> Unit) { PrimaryButton( text = stringResource(id = R.string.main_manage_tokens), - onClick = onManageTokensClick, + onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.spacing16), ) } -@Composable -private fun WalletBottomSheets(state: WalletState) { - val bottomSheetConfig = (state as? WalletState.ContentState)?.bottomSheetConfig - if (bottomSheetConfig != null) { - when (bottomSheetConfig.content) { - is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) - is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) - is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) - is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) +internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) { + (state as? WalletState.MultiCurrency)?.let { + (state.tokensListState as? WalletTokensListState.ContentState)?.let { + it.organizeTokensButtonConfig?.let { config -> + organizeTokensButton( + modifier = itemModifier, + isEnabled = config.isEnabled, + onClick = config.onClick, + ) + } } } -} - -// region Preview -@Preview -@Composable -private fun WalletScreenPreview_Light(@PreviewParameter(WalletScreenParameterProvider::class) state: WalletState) { - TangemTheme { - WalletScreen(state = state) - } -} - -@Preview -@Composable -private fun WalletScreenPreview_Dark(@PreviewParameter(WalletScreenParameterProvider::class) state: WalletState) { - TangemTheme(isDark = true) { - WalletScreen(state = state) - } -} - -private class WalletScreenParameterProvider : CollectionPreviewParameterProvider( - collection = listOf( - WalletPreviewData.multicurrencyWalletScreenState, - WalletPreviewData.singleWalletScreenState, - ), -) -// endregion Preview \ No newline at end of file +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreenV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreenV2.kt deleted file mode 100644 index 46380e8b44..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreenV2.kt +++ /dev/null @@ -1,256 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.FabPosition -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.paging.compose.collectAsLazyPagingItems -import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet -import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet -import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state2.model.* -import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet -import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList -import com.tangem.feature.wallet.presentation.wallet.ui.components.common.* -import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock -import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet -import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock -import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton -import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator -import kotlinx.collections.immutable.toImmutableList - -@Composable -internal fun WalletScreenV2(state: WalletScreenState) { - BackHandler(onBack = state.onBackClick) - - // It means that screen is still initializing - if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return - - val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex) - val snackbarHostState = remember(::SnackbarHostState) - val isAutoScroll = remember { mutableStateOf(value = false) } - - WalletContent( - state = state, - walletsListState = walletsListState, - snackbarHostState = snackbarHostState, - isAutoScroll = isAutoScroll, - onAutoScrollReset = { isAutoScroll.value = false }, - ) - - var alertConfig by remember { mutableStateOf(value = null) } - - alertConfig?.let { - WalletAlert(state = it, onDismiss = { alertConfig = null }) - } - - WalletEventEffectV2( - event = state.event, - selectedWalletIndex = state.selectedWalletIndex, - walletsListState = walletsListState, - snackbarHostState = snackbarHostState, - onAlertConfigSet = { alertConfig = it }, - onAutoScrollSet = { isAutoScroll.value = true }, - ) -} - -@Suppress("LongMethod") -@Composable -private fun WalletContent( - state: WalletScreenState, - walletsListState: LazyListState, - snackbarHostState: SnackbarHostState, - isAutoScroll: State, - onAutoScrollReset: () -> Unit, -) { - var selectedWalletIndex by remember { mutableIntStateOf(state.selectedWalletIndex) } - val selectedWallet = state.wallets[selectedWalletIndex] - - BaseScaffold(state = state, selectedWallet = selectedWallet, snackbarHostState = snackbarHostState) { - val movableItemModifier = Modifier.changeWalletAnimator(walletsListState) - - val lazyTxHistoryItems = (selectedWallet as? WalletState.SingleCurrency)?.let { walletState -> - (walletState.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems() - } - - val txHistoryItems by remember(selectedWallet.walletCardState.id, lazyTxHistoryItems?.itemCount) { - mutableStateOf(value = lazyTxHistoryItems) - } - - val betweenItemsPadding = TangemTheme.dimens.spacing14 - val horizontalPadding = TangemTheme.dimens.spacing16 - val itemModifier = movableItemModifier - .padding(top = betweenItemsPadding) - .padding(horizontal = horizontalPadding) - - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues( - top = TangemTheme.dimens.spacing8, - bottom = TangemTheme.dimens.spacing92, - ), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - item( - key = state.wallets.map { it.walletCardState.id }, - contentType = state.wallets.map { it.walletCardState.id }, - ) { - WalletsList( - lazyListState = walletsListState, - wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(), - isBalanceHidden = state.isHidingMode, - ) - } - - (selectedWallet as? WalletState.SingleCurrency)?.let { - controlButtons( - configs = it.buttons, - selectedWalletIndex = selectedWalletIndex, - modifier = movableItemModifier.padding(top = betweenItemsPadding), - ) - } - - notifications(configs = selectedWallet.warnings, modifier = itemModifier) - - (selectedWallet as? WalletState.SingleCurrency)?.let { walletState -> - walletState.marketPriceBlockState?.let { marketPriceBlockState -> - marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) - } - } - - (selectedWallet as? WalletState.Visa.Content)?.let { - depositButton( - modifier = itemModifier.fillMaxWidth(), - state = it.depositButtonState, - ) - - balancesAndLimitsBlock( - modifier = itemModifier, - state = it.balancesAndLimitBlockState, - ) - } - - contentItemsV2( - state = selectedWallet, - txHistoryItems = txHistoryItems, - isBalanceHidden = state.isHidingMode, - modifier = movableItemModifier, - ) - - organizeTokens(state = selectedWallet, itemModifier = itemModifier) - } - - val bottomSheetConfig = selectedWallet.bottomSheetConfig - if (bottomSheetConfig != null) { - when (bottomSheetConfig.content) { - is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) - is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) - is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) - is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) - is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) - } - } - - WalletsListEffectsV2( - lazyListState = walletsListState, - selectedWalletIndex = selectedWalletIndex, - onWalletChange = state.onWalletChange, - onSelectedWalletIndexSet = { selectedWalletIndex = it }, - isAutoScroll = isAutoScroll, - onAutoScrollReset = onAutoScrollReset, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun BaseScaffold( - state: WalletScreenState, - selectedWallet: WalletState, - snackbarHostState: SnackbarHostState, - content: @Composable () -> Unit, -) { - Scaffold( - topBar = { WalletTopBar(config = state.topBarConfig) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - floatingActionButton = { - val manageTokensButtonConfig by remember(state.selectedWalletIndex) { - mutableStateOf( - (state.wallets[state.selectedWalletIndex] as? WalletState.MultiCurrency)?.manageTokensButtonConfig, - ) - } - - manageTokensButtonConfig?.let { ManageTokensButton(onClick = it.onClick) } - }, - floatingActionButtonPosition = FabPosition.Center, - containerColor = TangemTheme.colors.background.secondary, - content = { - val pullRefreshState = rememberPullRefreshState( - refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - onRefresh = selectedWallet.pullToRefreshConfig.onRefresh, - ) - - Box( - modifier = Modifier - .pullRefresh(pullRefreshState) - .padding(it), - ) { - content() - - WalletPullToRefreshIndicator( - isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - } - }, - ) -} - -@Composable -private fun ManageTokensButton(onClick: () -> Unit) { - PrimaryButton( - text = stringResource(id = R.string.main_manage_tokens), - onClick = onClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16), - ) -} - -internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) { - (state as? WalletState.MultiCurrency)?.let { - (state.tokensListState as? WalletTokensListState.ContentState)?.let { - it.organizeTokensButtonConfig?.let { config -> - organizeTokensButton( - modifier = itemModifier, - isEnabled = config.isEnabled, - onClick = config.onClick, - ) - } - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt index 482d206fbf..5765897931 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffects.kt @@ -5,24 +5,34 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.snapshotFlow -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector import com.tangem.feature.wallet.presentation.wallet.ui.utils.WalletsListInteractionsCollector +@Suppress("LongParameterList") @Composable internal fun WalletsListEffects( lazyListState: LazyListState, - walletsListConfig: WalletsListConfig, + selectedWalletIndex: Int, + onWalletChange: (Int) -> Unit, + onSelectedWalletIndexSet: (Int) -> Unit, isAutoScroll: State, onAutoScrollReset: () -> Unit, ) { - LaunchedEffect(key1 = lazyListState, key2 = walletsListConfig.onWalletChange) { + LaunchedEffect(key1 = lazyListState, key2 = onWalletChange) { snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo } .collect( collector = ScrollOffsetCollector( + selectedWalletIndex = selectedWalletIndex, lazyListState = lazyListState, - walletsListConfig = walletsListConfig, - isAutoScroll = isAutoScroll, + onWalletChange = { newIndex -> + // Auto scroll must not change wallet + if (isAutoScroll.value) { + onSelectedWalletIndexSet(newIndex) + } else { + onSelectedWalletIndexSet(newIndex) + onWalletChange(newIndex) + } + }, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffectsV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffectsV2.kt deleted file mode 100644 index d7dc0bb129..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletsListEffectsV2.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui - -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.snapshotFlow -import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollectorV2 -import com.tangem.feature.wallet.presentation.wallet.ui.utils.WalletsListInteractionsCollector - -@Suppress("LongParameterList") -@Composable -internal fun WalletsListEffectsV2( - lazyListState: LazyListState, - selectedWalletIndex: Int, - onWalletChange: (Int) -> Unit, - onSelectedWalletIndexSet: (Int) -> Unit, - isAutoScroll: State, - onAutoScrollReset: () -> Unit, -) { - LaunchedEffect(key1 = lazyListState, key2 = onWalletChange) { - snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo } - .collect( - collector = ScrollOffsetCollectorV2( - selectedWalletIndex = selectedWalletIndex, - lazyListState = lazyListState, - onWalletChange = { newIndex -> - // Auto scroll must not change wallet - if (isAutoScroll.value) { - onSelectedWalletIndexSet(newIndex) - } else { - onSelectedWalletIndexSet(newIndex) - onWalletChange(newIndex) - } - }, - ), - ) - } - - LaunchedEffect(Unit) { - lazyListState.interactionSource.interactions.collect( - collector = WalletsListInteractionsCollector(onDragStart = onAutoScrollReset), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt index 104b25308d..865a52268f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt @@ -15,8 +15,8 @@ import com.tangem.core.ui.components.getWarningRowColors import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig import kotlinx.collections.immutable.ImmutableList @Composable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt index ff70bf8486..455cf810c4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt @@ -14,7 +14,10 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity @@ -23,51 +26,20 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList private const val SHORT_SNAP_ELEMENT_COUNT = 50 /** * Wallets list component * - * @param config config * @param lazyListState main content container list state * [REDACTED_AUTHOR] */ -@OptIn(ExperimentalFoundationApi::class) -@Composable -internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState, isBalanceHidden: Boolean) { - val horizontalCardPadding = TangemTheme.dimens.spacing16 - val screenWidth = LocalConfiguration.current.screenWidthDp.dp - val itemWidth by remember(screenWidth) { derivedStateOf { screenWidth - horizontalCardPadding * 2 } } - - LazyRow( - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), - state = lazyListState, - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - flingBehavior = rememberWalletsFlingBehaviour(lazyListState = lazyListState, itemWidth = itemWidth), - ) { - items( - items = config.wallets, - key = { it.id.stringValue }, - contentType = { it::class.java }, - ) { state -> - WalletCard( - state = state, - isBalanceHidden = isBalanceHidden, - modifier = Modifier - .animateItemPlacement() - .width(itemWidth), - ) - } - } -} - @OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletsList( @@ -136,8 +108,8 @@ private fun rememberWalletsFlingBehaviour(lazyListState: LazyListState, itemWidt private fun Preview_WalletsList_LightTheme() { TangemTheme(isDark = false) { WalletsList( - config = WalletPreviewData.walletListConfig, lazyListState = rememberLazyListState(), + wallets = WalletPreviewData.wallets.values.toPersistentList(), isBalanceHidden = false, ) } @@ -148,8 +120,8 @@ private fun Preview_WalletsList_LightTheme() { private fun Preview_WalletsList_DarkTheme() { TangemTheme(isDark = true) { WalletsList( - config = WalletPreviewData.walletListConfig, lazyListState = rememberLazyListState(), + wallets = WalletPreviewData.wallets.values.toPersistentList(), isBalanceHidden = false, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletBottomSheet.kt index b77393f8d6..1c16a6fc78 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletBottomSheet.kt @@ -1,7 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -19,7 +20,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig /** * Wallet bottom sheet with detail notification information diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 039920d7c4..eed63a9195 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -51,7 +51,7 @@ import com.tangem.core.ui.res.TangemDimens import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState private const val HALF_OF_ITEM_WIDTH = 0.5 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletContent.kt index 30cf4bb156..c4ffea5ea7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletContent.kt @@ -5,12 +5,8 @@ import androidx.compose.ui.Modifier import androidx.paging.compose.LazyPagingItems import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.txHistoryItems -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems -import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItemsV2 -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState as WalletStateV2 /** * Wallet content @@ -22,31 +18,19 @@ import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState as [REDACTED_AUTHOR] */ internal fun LazyListScope.contentItems( - state: WalletState.ContentState, + state: WalletState, txHistoryItems: LazyPagingItems?, isBalanceHidden: Boolean, modifier: Modifier = Modifier, ) { when (state) { - is WalletMultiCurrencyState -> tokensListItems(state.tokensListState, modifier, isBalanceHidden) - is WalletSingleCurrencyState -> txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier) - } -} - -internal fun LazyListScope.contentItemsV2( - state: WalletStateV2, - txHistoryItems: LazyPagingItems?, - isBalanceHidden: Boolean, - modifier: Modifier = Modifier, -) { - when (state) { - is WalletStateV2.MultiCurrency -> { - tokensListItemsV2(state.tokensListState, modifier, isBalanceHidden) + is WalletState.MultiCurrency -> { + tokensListItems(state.tokensListState, modifier, isBalanceHidden) } - is WalletStateV2.SingleCurrency -> { + is WalletState.SingleCurrency -> { txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier) } - is WalletStateV2.Visa -> { + is WalletState.Visa -> { txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt index f258a71ce9..476f3701e4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationWithBackground import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import kotlinx.collections.immutable.ImmutableList /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt index d22e971baf..4813eaff9f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletTopBar.kt @@ -7,7 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig /** * Wallet screen top bar diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index 9a855c797d..e0563a4400 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -17,10 +17,9 @@ import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.ImmutableList -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState as WalletTokensListStateV2 -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState.TokensListItemState as TokensListItemStateV2 private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST" @@ -38,56 +37,19 @@ internal fun LazyListScope.tokensListItems( isBalanceHidden: Boolean, ) { when (state) { - is WalletTokensListState.ContentState -> contentItems( - items = state.items, - isBalanceHidden = isBalanceHidden, - modifier = modifier, - ) - WalletTokensListState.Empty -> nonContentItem(modifier = modifier) - } -} - -internal fun LazyListScope.tokensListItemsV2( - state: WalletTokensListStateV2, - modifier: Modifier = Modifier, - isBalanceHidden: Boolean, -) { - when (state) { - is WalletTokensListStateV2.ContentState -> { - contentItemsV2( + is WalletTokensListState.ContentState -> { + contentItems( items = state.items, isBalanceHidden = isBalanceHidden, modifier = modifier, ) } - WalletTokensListStateV2.Empty -> nonContentItem(modifier = modifier) + WalletTokensListState.Empty -> nonContentItem(modifier = modifier) } } private fun LazyListScope.contentItems( - items: ImmutableList, - modifier: Modifier = Modifier, - isBalanceHidden: Boolean, -) { - itemsIndexed( - items = items, - key = { _, item -> item.id }, - contentType = { _, item -> item::class.java }, - itemContent = { index, item -> - MultiCurrencyContentItem( - state = item, - isBalanceHidden = isBalanceHidden, - modifier = modifier.roundedShapeItemDecoration( - currentIndex = index, - lastIndex = items.lastIndex, - ), - ) - }, - ) -} - -private fun LazyListScope.contentItemsV2( - items: ImmutableList, + items: ImmutableList, modifier: Modifier = Modifier, isBalanceHidden: Boolean, ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt index d94ee34b63..9d39d7fcbc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt @@ -5,8 +5,7 @@ import androidx.compose.ui.Modifier import com.tangem.core.ui.extensions.resolveReference import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem import com.tangem.feature.wallet.presentation.common.component.TokenItem -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensListState.TokensListItemState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState /** * Multi-currency content item @@ -16,22 +15,6 @@ import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletTokensLi * [REDACTED_AUTHOR] */ -@Composable -internal fun MultiCurrencyContentItem( - state: WalletTokensListState.TokensListItemState, - isBalanceHidden: Boolean, - modifier: Modifier = Modifier, -) { - when (state) { - is WalletTokensListState.TokensListItemState.NetworkGroupTitle -> { - NetworkGroupItem(networkName = state.name.resolveReference(), modifier = modifier) - } - is WalletTokensListState.TokensListItemState.Token -> { - TokenItem(state = state.state, isBalanceHidden = isBalanceHidden, modifier = modifier) - } - } -} - @Composable internal fun MultiCurrencyContentItem( state: TokensListItemState, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt index af4d8c17ae..4b73b51b81 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBlock.kt index 5855ab22f6..6fe4af13e5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBlock.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBlockState +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState private const val BALANCES_AND_LIMITS_BLOCK_KEY = "BalancesAndLimitsBlock" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt index bd590c3ac4..c2e6a39b4d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/BalancesAndLimitsBottomSheet.kt @@ -13,15 +13,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig @Composable internal fun BalancesAndLimitsBottomSheet(config: TangemBottomSheetConfig) { @@ -45,136 +42,92 @@ private fun BalancesAndLimitsContent(config: BalancesAndLimitsBottomSheetConfig, ) }, firstBlock = { - BlockContent( - title = stringReference("Balance, ${config.currency}"), - content = { - BlockItem( - title = stringReference("Total"), - value = config.balance.totalBalance, - ) - BlockItem( - title = stringReference("AML Verified"), - value = config.balance.amlVerified, - ) - BlockItem( - title = stringReference("Available"), - value = config.balance.availableBalance, - ) - BlockItem( - title = stringReference("Blocked"), - value = config.balance.blockedBalance, - ) - BlockItem( - title = stringReference("Debit"), - value = config.balance.debit, - ) - BlockItem( - title = stringReference("Pending refund"), - value = config.balance.pending, - ) - }, - onInfoIconClick = config.onBalanceInfoClick, - ) + BalancesBlock(balances = config.balance) }, secondBlock = { - BlockContent( - title = stringReference("Limits, ${config.currency}"), - description = stringReference("Available by ${config.limit.availableBy}"), - content = { - BlockItem( - title = stringReference("In-store (otp)"), - value = config.limit.inStore, - ) - BlockItem( - title = stringReference("Other (no-otp)"), - value = config.limit.other, - ) - BlockItem( - title = stringReference("Single transaction"), - value = config.limit.singleTransaction, - ) - }, - onInfoIconClick = config.onBalanceInfoClick, - ) + LimitsBlock(limits = config.limit) }, ) } @Composable -private inline fun BlockContent( - title: TextReference, - content: @Composable ColumnScope.() -> Unit, - noinline onInfoIconClick: () -> Unit, - modifier: Modifier = Modifier, - description: TextReference? = null, -) { - Column( - modifier = modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth() - .background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.roundedCornersXMedium, +private fun BalancesBlock(balances: BalancesAndLimitsBottomSheetConfig.Balance, modifier: Modifier = Modifier) { + BlockContent( + modifier = modifier, + title = stringReference("Balance"), + content = { + BlockItem( + title = stringReference("Total"), + value = balances.totalBalance, ) - .padding(vertical = TangemTheme.dimens.spacing8), - ) { - Row( - modifier = Modifier - .padding( - start = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing4, - ) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = title.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, + BlockItem( + title = stringReference("AML Verified"), + value = balances.amlVerified, ) - SpacerWMax() - if (description != null) { - Text( - text = description.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - ) - } - IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), - onClick = onInfoIconClick, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size16), - painter = painterResource(id = R.drawable.ic_information_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } - } - content() - } + BlockItem( + title = stringReference("Available"), + value = balances.availableBalance, + ) + BlockItem( + title = stringReference("Blocked"), + value = balances.blockedBalance, + ) + BlockItem( + title = stringReference("Debit"), + value = balances.debit, + ) + BlockItem( + title = stringReference("Pending refund"), + value = balances.pending, + ) + }, + description = { + InfoButton(onClick = balances.onInfoClick) + }, + ) } @Composable -private fun BlockItem(title: TextReference, value: String, modifier: Modifier = Modifier) { - Row( - modifier = modifier - .padding(horizontal = TangemTheme.dimens.spacing12) - .fillMaxWidth() - .heightIn(min = TangemTheme.dimens.size32), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, +private fun LimitsBlock(limits: BalancesAndLimitsBottomSheetConfig.Limit, modifier: Modifier = Modifier) { + BlockContent( + modifier = modifier, + title = stringReference("Limits"), + content = { + BlockItem( + title = stringReference("In-store (otp)"), + value = limits.inStore, + ) + BlockItem( + title = stringReference("Other (no-otp)"), + value = limits.other, + ) + BlockItem( + title = stringReference("Single transaction"), + value = limits.singleTransaction, + ) + }, + description = { + Text( + text = "Available till ${limits.availableBy}", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + InfoButton(onClick = limits.onInfoClick) + }, + ) +} + +@Composable +private fun InfoButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + IconButton( + modifier = modifier.size(TangemTheme.dimens.size32), + onClick = onClick, ) { - Text( - text = title.resolveReference(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - ) - Text( - text = value, - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.primary1, + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_information_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, ) } } @@ -229,23 +182,22 @@ private class BalancesAndLimitsBottomSheetParameterProvider : CollectionPreviewParameterProvider( collection = listOf( BalancesAndLimitsBottomSheetConfig( - currency = "USDT", balance = BalancesAndLimitsBottomSheetConfig.Balance( - totalBalance = "492.45", - availableBalance = "392.45", - blockedBalance = "36.00", - debit = "00.00", - pending = "20.99", - amlVerified = "356.45", + totalBalance = "492.45 USDT", + availableBalance = "392.45 USDT", + blockedBalance = "36.00 USDT", + debit = "00.00 USDT", + pending = "20.99 USDT", + amlVerified = "356.45 USDT", + onInfoClick = {}, ), limit = BalancesAndLimitsBottomSheetConfig.Limit( - availableBy = "Nov, 11", - inStore = "563.00", - other = "100.00", - singleTransaction = "100.00", + availableBy = "Nov, 11 USDT", + inStore = "563.00 USDT", + other = "100.00 USDT", + singleTransaction = "100.00 USDT", + onInfoClick = {}, ), - onBalanceInfoClick = {}, - onLimitInfoClick = {}, ), ), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/Blocks.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/Blocks.kt new file mode 100644 index 0000000000..723fabf3bc --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/Blocks.kt @@ -0,0 +1,83 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.visa + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme + +private const val BLOCK_ITEM_NAME_WEIGHT = .45f +private const val BLOCK_ITEM_VALUE_WEIGHT = .55f + +@Composable +internal inline fun BlockContent( + title: TextReference, + content: @Composable ColumnScope.() -> Unit, + modifier: Modifier = Modifier, + description: @Composable RowScope.() -> Unit = {}, +) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth() + .background( + color = TangemTheme.colors.background.primary, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + ) { + Row( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing12) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size42), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + SpacerWMax() + description() + } + content() + SpacerH8() + } +} + +@Composable +internal fun BlockItem(title: TextReference, value: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size32) + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Text( + modifier = Modifier.weight(BLOCK_ITEM_NAME_WEIGHT), + text = title.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Start, + ) + Text( + modifier = Modifier.weight(BLOCK_ITEM_VALUE_WEIGHT), + text = value, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.End, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaDepositButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaDepositButton.kt index 85dab308e0..30bb253d24 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaDepositButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaDepositButton.kt @@ -4,7 +4,7 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state2.model.DepositButtonState +import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState private const val DEPOSIT_BUTTON_CONTENT_TYPE = "DepositButton" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaTxDetailsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaTxDetailsBottomSheet.kt new file mode 100644 index 0000000000..eafdaf3801 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/VisaTxDetailsBottomSheet.kt @@ -0,0 +1,286 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.visa + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.SpacerW12 +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun VisaTxDetailsBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.secondary, + ) { content: VisaTxDetailsBottomSheetConfig -> + VisaTxDetailsBottomSheetContent(content) + } +} + +@Composable +private fun VisaTxDetailsBottomSheetContent(config: VisaTxDetailsBottomSheetConfig, modifier: Modifier = Modifier) { + ContentContainer( + modifier = modifier, + blocksCount = config.requests.size.inc(), + title = { + Text( + text = "Transaction Details", + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + }, + block = { index -> + if (index == 0) { + TransactionBlock(config.transaction) + } else { + BlockchainRequestBlock(config.requests[index - 1]) + } + }, + ) +} + +@Composable +private fun TransactionBlock(transaction: VisaTxDetailsBottomSheetConfig.Transaction, modifier: Modifier = Modifier) { + BlockContent( + modifier = modifier, + title = stringReference(value = "Transaction"), + content = { + BlockItem( + title = stringReference(value = "Type"), + value = transaction.type, + ) + BlockItem( + title = stringReference(value = "Status"), + value = transaction.status, + ) + BlockItem( + title = stringReference(value = "Blockchain Amount"), + value = transaction.blockchainAmount, + ) + BlockItem( + title = stringReference(value = "Blockchain Fee"), + value = transaction.blockchainFee, + ) + BlockItem( + title = stringReference(value = "Transaction Amount"), + value = transaction.transactionAmount, + ) + BlockItem( + title = stringReference(value = "Currency Code"), + value = transaction.transactionCurrencyCode, + ) + BlockItem( + title = stringReference(value = "Merchant Name"), + value = transaction.merchantName, + ) + BlockItem( + title = stringReference(value = "Merchant City"), + value = transaction.merchantCity, + ) + BlockItem( + title = stringReference(value = "Merchant Country Code"), + value = transaction.merchantCountryCode, + ) + BlockItem( + title = stringReference(value = "Merchant Category Code"), + value = transaction.merchantCategoryCode, + ) + }, + ) +} + +@Composable +private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Request, modifier: Modifier = Modifier) { + BlockContent( + modifier = modifier, + title = stringReference(value = "Blockchain request"), + description = { + if (request.onExploreClick == null) return + + Row( + modifier = Modifier.clickable(onClick = request.onExploreClick), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4), + ) { + Icon( + painter = painterResource(id = R.drawable.ic_compass_24), + contentDescription = null, + modifier = Modifier.size(size = TangemTheme.dimens.size18), + tint = TangemTheme.colors.icon.informative, + ) + Text( + text = "Explore", + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption1, + ) + } + SpacerW12() + }, + content = { + BlockItem( + title = stringReference(value = "Type"), + value = request.type, + ) + BlockItem( + title = stringReference(value = "Status"), + value = request.status, + ) + BlockItem( + title = stringReference(value = "Blockchain Amount"), + value = request.blockchainAmount, + ) + BlockItem( + title = stringReference(value = "Blockchain Fee"), + value = request.blockchainFee, + ) + BlockItem( + title = stringReference(value = "Transaction Amount"), + value = request.transactionAmount, + ) + BlockItem( + title = stringReference(value = "Currency Code"), + value = request.currencyCode, + ) + BlockItem( + title = stringReference(value = "Error Code"), + value = request.errorCode.toString(), + ) + BlockItem( + title = stringReference(value = "Date"), + value = request.date, + ) + BlockItem( + title = stringReference(value = "Tx Hash"), + value = request.txHash, + ) + BlockItem( + title = stringReference(value = "Tx Status"), + value = request.txStatus, + ) + }, + ) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun ContentContainer( + blocksCount: Int, + title: @Composable BoxScope.() -> Unit, + block: @Composable ColumnScope.(Int) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier.background(TangemTheme.colors.background.secondary), + contentPadding = PaddingValues( + bottom = TangemTheme.dimens.spacing16, + ), + verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + stickyHeader { + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size44) + .background(TangemTheme.colors.background.secondary), + contentAlignment = Alignment.Center, + content = title, + ) + } + items(blocksCount) { index -> + Column { + block(index) + } + } + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun VisaTxDetailsBottomSheetPreview_Light( + @PreviewParameter(VisaTxDetailsBottomSheetParameterProvider::class) state: VisaTxDetailsBottomSheetConfig, +) { + TangemTheme { + VisaTxDetailsBottomSheetContent(state) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun VisaTxDetailsBottomSheetPreview_Dark( + @PreviewParameter(VisaTxDetailsBottomSheetParameterProvider::class) state: VisaTxDetailsBottomSheetConfig, +) { + TangemTheme(isDark = true) { + VisaTxDetailsBottomSheetContent(state) + } +} + +private class VisaTxDetailsBottomSheetParameterProvider : + CollectionPreviewParameterProvider( + collection = listOf( + VisaTxDetailsBottomSheetConfig( + transaction = VisaTxDetailsBottomSheetConfig.Transaction( + id = "518385816101345408", + type = "payment", + status = "authorized", + blockchainAmount = "1.0614 USDT", + blockchainFee = "0.12", + transactionAmount = "0.99 €", + transactionCurrencyCode = "978", + merchantName = "SQ *FORMATIVE", + merchantCity = "London", + merchantCountryCode = "GB", + merchantCategoryCode = "5814", + ), + requests = persistentListOf( + VisaTxDetailsBottomSheetConfig.Request( + id = "524582128501966718", + type = "authorize_payment", + status = "accepted", + blockchainAmount = "1.0593 USDT", + blockchainFee = "0.10", + transactionAmount = "0.99 €", + currencyCode = "978", + errorCode = 0, + date = "2023-12-01 14:20:09.230 +0300", + txHash = "0xc458f0204fe43b82c775004baabb38435b5595f4307d8c3ac74625c827be7c29", + txStatus = "confirmed", + onExploreClick = {}, + ), + VisaTxDetailsBottomSheetConfig.Request( + id = "524582128501966799", + type = "settlement", + status = "accepted", + blockchainAmount = "1.0614 USDT", + blockchainFee = "0.12", + transactionAmount = "0.99 €", + currencyCode = "978", + errorCode = 0, + date = "2023-12-01 00:01:00.000 +0300", + txHash = "0x635841d5fbdf1087cdd929019c863ee88a7165e4340bc17ddd0b1d04dfb11daa", + txStatus = "confirmed", + onExploreClick = {}, + ), + ), + ), + ), + ) + +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt index 2119079f09..939886db4a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollector.kt @@ -2,8 +2,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.State -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig import kotlinx.coroutines.flow.FlowCollector import kotlin.math.abs @@ -12,46 +10,40 @@ import kotlin.math.abs * If first visible item offset is greater than half item size, then change selected wallet index. * If last visible item offset is greater than half item size, then change selected wallet index. * + * @param selectedWalletIndex selected wallet index * @property lazyListState lazy list state - * @property walletsListConfig wallets list config - * @property isAutoScroll check if last scrolling is auto scroll + * @property onWalletChange callback that will be invoked on wallet change * [REDACTED_AUTHOR] */ internal class ScrollOffsetCollector( + selectedWalletIndex: Int, private val lazyListState: LazyListState, - private val walletsListConfig: WalletsListConfig, - private val isAutoScroll: State, + private val onWalletChange: (Int) -> Unit, ) : FlowCollector> { private val LazyListItemInfo.halfItemSize get() = size.div(other = 2) - private var currentIndex = walletsListConfig.selectedWalletIndex + private var currentIndex = selectedWalletIndex override suspend fun emit(value: List) { - // Auto scroll must not change wallet - if (isAutoScroll.value) { - currentIndex = walletsListConfig.selectedWalletIndex - return - } - if (!lazyListState.isScrollInProgress || value.size <= 1) return val firstItem = value.firstOrNull() ?: return val lastItem = value.lastOrNull() ?: return if (abs(firstItem.offset) > firstItem.halfItemSize) { - onWalletChange(newIndex = firstItem.index + 1) + selectIndex(newIndex = firstItem.index + 1) } else if (abs(lastItem.offset) > lastItem.halfItemSize) { - onWalletChange(newIndex = lastItem.index - 1) + selectIndex(newIndex = lastItem.index - 1) } } - private fun onWalletChange(newIndex: Int) { + private fun selectIndex(newIndex: Int) { if (currentIndex != newIndex) { currentIndex = newIndex - walletsListConfig.onWalletChange(newIndex) + onWalletChange(newIndex) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollectorV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollectorV2.kt deleted file mode 100644 index 43a4af298b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ScrollOffsetCollectorV2.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.utils - -import androidx.compose.foundation.lazy.LazyListItemInfo -import androidx.compose.foundation.lazy.LazyListState -import kotlinx.coroutines.flow.FlowCollector -import kotlin.math.abs - -/** - * Flow collector for scroll items tracking. - * If first visible item offset is greater than half item size, then change selected wallet index. - * If last visible item offset is greater than half item size, then change selected wallet index. - * - * @param selectedWalletIndex selected wallet index - * @property lazyListState lazy list state - * @property onWalletChange callback that will be invoked on wallet change - * -[REDACTED_AUTHOR] - */ -internal class ScrollOffsetCollectorV2( - selectedWalletIndex: Int, - private val lazyListState: LazyListState, - private val onWalletChange: (Int) -> Unit, -) : FlowCollector> { - - private val LazyListItemInfo.halfItemSize - get() = size.div(other = 2) - - private var currentIndex = selectedWalletIndex - - override suspend fun emit(value: List) { - if (!lazyListState.isScrollInProgress || value.size <= 1) return - - val firstItem = value.firstOrNull() ?: return - val lastItem = value.lastOrNull() ?: return - - if (abs(firstItem.offset) > firstItem.halfItemSize) { - selectIndex(newIndex = firstItem.index + 1) - } else if (abs(lastItem.offset) > lastItem.halfItemSize) { - selectIndex(newIndex = lastItem.index - 1) - } - } - - private fun selectIndex(newIndex: Int) { - if (currentIndex != newIndex) { - currentIndex = newIndex - onWalletChange(newIndex) - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsScrollPreviewExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsScrollPreviewExt.kt index 3dbb6ccb11..f75f58b867 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsScrollPreviewExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/WalletsScrollPreviewExt.kt @@ -4,7 +4,7 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.lazy.LazyListState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt deleted file mode 100644 index e9ef5cc769..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import java.math.BigDecimal - -internal class CryptoCurrencyStatusToTokenItemConverter( - private val appCurrencyProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - - override fun convert(value: CryptoCurrencyStatus): TokenItemState { - return when (value.value) { - is CryptoCurrencyStatus.Loading -> value.mapToLoadingState() - is CryptoCurrencyStatus.Loaded, - is CryptoCurrencyStatus.Custom, - is CryptoCurrencyStatus.NoQuote, - is CryptoCurrencyStatus.NoAccount, - -> value.mapToTokenItemState() - is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState() - is CryptoCurrencyStatus.Unreachable, - is CryptoCurrencyStatus.NoAmount, - -> value.mapToUnreachableTokenItemState() - } - } - - private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading { - return TokenItemState.Loading( - id = currency.id.value, - iconState = iconStateConverter.convert(value = this), - titleState = TokenItemState.TitleState.Content(text = currency.name), - ) - } - - private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content { - return TokenItemState.Content( - id = currency.id.value, - iconState = iconStateConverter.convert(value = this), - titleState = TokenItemState.TitleState.Content( - text = currency.name, - hasPending = value.hasCurrentNetworkTransactions, - ), - fiatAmountState = TokenItemState.FiatAmountState.Content( - text = getFormattedFiatAmount(), - ), - cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()), - cryptoPriceState = getCryptoPriceState(), - onItemClick = { clickIntents.onTokenItemClick(currency) }, - onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, - ) - } - - private fun CryptoCurrencyStatus.getFormattedAmount(): String { - val amount = value.amount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN - - return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) - } - - private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { - val fiatAmount = value.fiatAmount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN - val appCurrency = appCurrencyProvider() - - return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) - } - - private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable( - id = currency.id.value, - iconState = iconStateConverter.convert(value = this), - titleState = TokenItemState.TitleState.Content(text = currency.name), - onItemClick = { clickIntents.onTokenItemClick(currency) }, - onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, - ) - - private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState() = TokenItemState.NoAddress( - id = currency.id.value, - iconState = iconStateConverter.convert(this), - titleState = TokenItemState.TitleState.Content(text = currency.name), - onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, - ) - - private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.CryptoPriceState { - val fiatRate = value.fiatRate - val priceChange = value.priceChange - - return if (fiatRate != null && priceChange != null) { - TokenItemState.CryptoPriceState.Content( - price = fiatRate.getFormattedCryptoPrice(), - priceChangePercent = BigDecimalFormatter.formatPercent( - percent = priceChange, - useAbsoluteValue = true, - maxFractionDigits = 1, - minFractionDigits = 1, - ), - type = priceChange.getPriceChangeType(), - ) - } else { - TokenItemState.CryptoPriceState.Unknown - } - } - - private fun BigDecimal.getFormattedCryptoPrice(): String { - val appCurrency = appCurrencyProvider() - return BigDecimalFormatter.formatFiatAmount( - fiatAmount = this, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) - } - - private fun BigDecimal.getPriceChangeType(): PriceChangeType { - return if (this > BigDecimal.ZERO) PriceChangeType.UP else PriceChangeType.DOWN - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CurrencyStatusErrorConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CurrencyStatusErrorConverter.kt deleted file mode 100644 index 19f35776b0..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CurrencyStatusErrorConverter.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -// TODO: Implement this -internal class CurrencyStatusErrorConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: CurrencyStatusError): WalletSingleCurrencyState.Content { - return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt deleted file mode 100644 index 3bd3dc16d5..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.TokenList.FiatBalance -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory -import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class FiatBalanceToWalletCardConverter( - private val currentState: WalletCardState, - private val appCurrencyProvider: Provider, - private val currentWalletProvider: Provider, -) : Converter { - - override fun convert(value: FiatBalance): WalletCardState { - return when (value) { - is FiatBalance.Loading -> currentState.toLoadingWalletCardState() - is FiatBalance.Failed -> currentState.toErrorWalletCardState() - is FiatBalance.Loaded -> value.convertToWalletCardState() - } - } - - private fun WalletCardState.toLoadingWalletCardState(): WalletCardState { - return WalletCardState.Loading(id, title, additionalInfo, imageResId, onRenameClick, onDeleteClick) - } - - private fun WalletCardState.toErrorWalletCardState(): WalletCardState { - return WalletCardState.Error( - id = id, - title = title, - additionalInfo = additionalInfo, - imageResId = imageResId, - onDeleteClick = onDeleteClick, - onRenameClick = onRenameClick, - ) - } - - private fun FiatBalance.Loaded.convertToWalletCardState(): WalletCardState { - val appCurrency = appCurrencyProvider() - - return WalletCardState.Content( - id = currentState.id, - title = currentState.title, - additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()), - imageResId = currentState.imageResId, - onRenameClick = currentState.onRenameClick, - onDeleteClick = currentState.onDeleteClick, - balance = formatFiatAmount( - fiatAmount = this.amount, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), - cardCount = currentWalletProvider().getCardsCount(), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt deleted file mode 100644 index 66508d3110..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/HiddenStateConverter.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class HiddenStateConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: Boolean): WalletState { - return when (val state = currentStateProvider() as? WalletState.ContentState) { - is WalletMultiCurrencyState.Content -> { - state.copy(isBalanceHidden = value) - } - - is WalletSingleCurrencyState.Content -> { - state.copy(isBalanceHidden = value) - } - - else -> currentStateProvider() - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt deleted file mode 100644 index 512fd08483..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class TokenListErrorConverter( - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: TokenListError): WalletState { - val state = currentStateProvider() - return when (value) { - is TokenListError.EmptyTokens -> state.mapToEmptyTokensState() - is TokenListError.DataError, - is TokenListError.UnableToSortTokenList, - -> state - } - } - - private fun WalletState.mapToEmptyTokensState(): WalletState { - return when (this) { - is WalletMultiCurrencyState.Content -> copy(tokensListState = WalletTokensListState.Empty) - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Content, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> this - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt deleted file mode 100644 index 0f40074e67..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkGroup -import com.tangem.domain.tokens.model.TokenList -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState -import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.mutate -import kotlinx.collections.immutable.persistentListOf - -internal class TokenListToContentItemsConverter( - appCurrencyProvider: Provider, - private val clickIntents: WalletClickIntents, -) : Converter { - - private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter( - appCurrencyProvider = appCurrencyProvider, - clickIntents = clickIntents, - ) - - override fun convert(value: TokenListWithWallet): WalletTokensListState { - val isSingleCurrencyWalletWithToken = !value.wallet.isMultiCurrency && - value.wallet.scanResponse.walletData?.token != null - return when (val tokenList = value.tokenList) { - is TokenList.Empty -> WalletTokensListState.Empty - is TokenList.GroupedByNetwork -> WalletTokensListState.Content( - items = tokenList.mapToMultiCurrencyItems(), - organizeTokensButton = tokenList.mapToOrganizeTokensButtonState(isSingleCurrencyWalletWithToken), - ) - is TokenList.Ungrouped -> WalletTokensListState.Content( - items = tokenList.mapToMultiCurrencyItems(), - organizeTokensButton = tokenList.mapToOrganizeTokensButtonState(isSingleCurrencyWalletWithToken), - ) - } - } - - private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList { - return groups.fold(initial = persistentListOf()) { acc, group -> - acc.mutate { it.addGroup(group) } - } - } - - private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList { - return currencies.fold(initial = persistentListOf()) { acc, token -> - acc.mutate { it.addToken(token) } - } - } - - private fun TokenList.GroupedByNetwork.mapToOrganizeTokensButtonState( - isSingleCurrencyWithTokenWallet: Boolean, - ): OrganizeTokensButtonState { - return getOrganizeTokensButtonState( - isLoading = totalFiatBalance is TokenList.FiatBalance.Loading, - currenciesSize = groups.flatMap(NetworkGroup::currencies).size, - isSingleCurrencyWithTokenWallet = isSingleCurrencyWithTokenWallet, - ) - } - - private fun TokenList.Ungrouped.mapToOrganizeTokensButtonState( - isSingleCurrencyWithTokenWallet: Boolean, - ): OrganizeTokensButtonState { - return getOrganizeTokensButtonState( - isLoading = totalFiatBalance is TokenList.FiatBalance.Loading, - currenciesSize = currencies.size, - isSingleCurrencyWithTokenWallet = isSingleCurrencyWithTokenWallet, - ) - } - - private fun MutableList.addGroup(group: NetworkGroup): List { - val groupTitle = TokensListItemState.NetworkGroupTitle( - id = group.network.hashCode(), - name = stringReference(group.network.name), - ) - - this.add(groupTitle) - - group.currencies.forEach { token -> - this.addToken(token) - } - - return this - } - - private fun MutableList.addToken(token: CryptoCurrencyStatus): List { - val tokenItemState = tokenStatusConverter.convert(token) - - this.add(TokensListItemState.Token(tokenItemState)) - - return this - } - - private fun getOrganizeTokensButtonState( - isLoading: Boolean, - currenciesSize: Int, - isSingleCurrencyWithTokenWallet: Boolean, - ): OrganizeTokensButtonState { - return if (currenciesSize > 1 && !isSingleCurrencyWithTokenWallet) { - OrganizeTokensButtonState.Visible( - isEnabled = !isLoading, - onClick = clickIntents::onOrganizeTokensClick, - ) - } else { - OrganizeTokensButtonState.Hidden - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt deleted file mode 100644 index 823ce6e272..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.utils - -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig -import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet -import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toPersistentList - -@Suppress("LongParameterList") -internal class TokenListToWalletStateConverter( - private val currentStateProvider: Provider, - private val currentWalletProvider: Provider, - private val appCurrencyProvider: Provider, - clickIntents: WalletClickIntents, -) : Converter { - - private val tokenListToContentConverter = TokenListToContentItemsConverter( - appCurrencyProvider = appCurrencyProvider, - clickIntents = clickIntents, - ) - - override fun convert(value: TokenListWithWallet): WalletState { - val tokenList = value.tokenList - val isSingleCurrencyWalletWithToken = value.wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() - return when (val state = currentStateProvider()) { - is WalletMultiCurrencyState.Content -> { - state.copy( - walletsListConfig = state.updateSelectedWallet(fiatBalance = tokenList.totalFiatBalance), - tokensListState = tokenListToContentConverter.convert(value = value), - isManageTokensAvailable = !isSingleCurrencyWalletWithToken, - ) - } - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Content, - is WalletSingleCurrencyState.Locked, - is WalletState.Initial, - -> state - } - } - - private fun WalletMultiCurrencyState.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig { - val selectedWalletIndex = walletsListConfig.selectedWalletIndex - val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex] - val converter = FiatBalanceToWalletCardConverter( - currentState = selectedWalletCard, - currentWalletProvider = currentWalletProvider, - appCurrencyProvider = appCurrencyProvider, - ) - - return walletsListConfig.copy( - wallets = walletsListConfig.wallets.toPersistentList() - .set(index = selectedWalletIndex, element = converter.convert(fiatBalance)), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt deleted file mode 100644 index 5b12a1cf6b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.models.UserWalletId - -@Suppress("TooManyFunctions") -internal interface WalletClickIntents { - - fun onBackClick() - - fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) - - fun onScanToUnlockWalletClick() - - fun onDetailsClick() - - fun onBackupCardClick() - - fun onSignedHashesNotificationCloseClick() - - fun onLikeAppClick() - - fun onDislikeAppClick() - - fun onCloseRateAppNotificationClick() - - fun onWalletChange(index: Int) - - fun onRefreshSwipe() - - fun onOrganizeTokensClick() - - fun onUnlockWalletClick() - - fun onUnlockWalletNotificationClick() - - fun onDismissBottomSheet() - - fun onTokenItemClick(currency: CryptoCurrency) - - fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onRenameBeforeConfirmationClick(userWalletId: UserWalletId) - - fun onRenameAfterConfirmationClick(userWalletId: UserWalletId, name: String) - - fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId) - - fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) - - fun onSingleCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus? = null) - - fun onMultiCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onManageTokensClick() - - fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onReloadClick() - - fun onExploreClick() - - fun onTransactionClick(txHash: String) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt deleted file mode 100644 index da74cc71c1..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt +++ /dev/null @@ -1,213 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import arrow.core.Either -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase -import com.tangem.domain.tokens.error.GetCurrenciesError -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.feature.wallet.presentation.wallet.domain.HasSingleWalletSignedHashesUseCase -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.flowOf - -/** - * Wallet notifications list factory - * - * @property isDemoCardUseCase use case that checks if card is demo - * @property isReadyToShowRateAppUseCase use case that checks if card is user already rate app - * @property isNeedToBackupUseCase use case that checks if wallet need backup cards - * @property getMissedAddressCryptoCurrenciesUseCase use case that gets missed address crypto currencies - * @property hasSingleWalletSignedHashesUseCase use case that checks if single wallet signed hashes - * @property shouldShowSwapPromoWalletUseCase use case that checks if should show swap promo - * @property clickIntents screen click intents - * -[REDACTED_AUTHOR] - */ -@Suppress("LongParameterList") -internal class WalletNotificationsListFactory( - private val isDemoCardUseCase: IsDemoCardUseCase, - private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, - private val isNeedToBackupUseCase: IsNeedToBackupUseCase, - private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, - private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase, - private val clickIntents: WalletClickIntents, -) { - - private var readyForRateAppNotification = false - - fun create( - selectedWallet: UserWallet, - cryptoCurrencyList: List, - ): Flow> { - val cardTypesResolver = selectedWallet.scanResponse.cardTypesResolver - return combine( - flow = hasSingleWalletSignedHashesFlow(selectedWallet, cryptoCurrencyList), - flow2 = isReadyToShowRateAppUseCase().conflate(), - flow3 = isNeedToBackupUseCase(selectedWallet.walletId).conflate(), - flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWallet.walletId).conflate(), - ) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies -> - readyForRateAppNotification = true - buildList { - addCriticalNotifications(cardTypesResolver) - - addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies) - - addWarningNotifications(cardTypesResolver, cryptoCurrencyList, hasSignedHashes, isNeedToBackup) - - addRateTheAppNotification(isReadyToShowRating) - }.toImmutableList() - } - } - - private fun hasSingleWalletSignedHashesFlow( - selectedWallet: UserWallet, - cryptoCurrencyList: List, - ): Flow { - return if (selectedWallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { - flowOf(value = false) - } else { - val network = requireNotNull(cryptoCurrencyList.firstOrNull()?.currency?.network) - hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = network).conflate() - } - } - - private fun MutableList.addCriticalNotifications(cardTypesResolver: CardTypesResolver) { - addIf( - element = WalletNotification.Critical.DevCard, - condition = !cardTypesResolver.isReleaseFirmwareType(), - ) - - addIf( - element = WalletNotification.Critical.FailedCardValidation, - condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(), - ) - - cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures -> - addIf( - element = WalletNotification.Warning.LowSignatures(count = remainingSignatures), - condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT, - ) - } - } - - private fun MutableList.addRateTheAppNotification(isReadyToShowRating: Boolean) { - addIf( - element = WalletNotification.RateApp( - onLikeClick = clickIntents::onLikeAppClick, - onDislikeClick = clickIntents::onDislikeAppClick, - onCloseClick = clickIntents::onCloseRateAppNotificationClick, - ), - condition = isReadyToShowRating && readyForRateAppNotification, - ) - } - - private fun MutableList.addWarningNotifications( - cardTypesResolver: CardTypesResolver, - cryptoCurrencyList: List, - hasSignedHashes: Boolean, - isNeedToBackup: Boolean, - ) { - addIf( - element = WalletNotification.Warning.MissingBackup( - onStartBackupClick = clickIntents::onBackupCardClick, - ), - condition = isNeedToBackup, - ) - - addIf( - element = WalletNotification.Warning.TestNetCard, - condition = cardTypesResolver.isTestCard(), - ) - - if (cardTypesResolver.isMultiwalletAllowed()) { - addIf( - element = WalletNotification.Warning.SomeNetworksUnreachable, - condition = cryptoCurrencyList.hasUnreachableNetworks(), - ) - } else { - addIf( - element = WalletNotification.Warning.NetworksUnreachable, - condition = cryptoCurrencyList.hasUnreachableNetworks(), - ) - - addNoAccountWarning(cryptoCurrencyList) - - addIf( - element = WalletNotification.Warning.NumberOfSignedHashesIncorrect( - onCloseClick = clickIntents::onSignedHashesNotificationCloseClick, - ), - condition = hasSignedHashes, - ) - } - } - - private fun MutableList.addInformationalNotifications( - cardTypesResolver: CardTypesResolver, - maybeMissedAddressCurrencies: Either>, - ) { - addIf( - element = WalletNotification.Informational.DemoCard, - condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()), - ) - - addMissingAddressesNotification(maybeMissedAddressCurrencies) - } - - private fun MutableList.addIf(element: WalletNotification, condition: Boolean) { - if (condition) { - add(element = element) - if (element is WalletNotification.Critical || element is WalletNotification.Warning) { - readyForRateAppNotification = false - } - } - } - - private fun MutableList.addMissingAddressesNotification( - maybeCurrencies: Either>, - ) { - val missingAddressCurrencies = (maybeCurrencies as? Either.Right)?.value ?: return - - addIf( - element = WalletNotification.Informational.MissingAddresses( - missingAddressesCount = missingAddressCurrencies.count(), - onGenerateClick = { - clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = missingAddressCurrencies) - }, - ), - condition = missingAddressCurrencies.isNotEmpty(), - ) - } - - private fun List.hasUnreachableNetworks(): Boolean { - return any { it.value is CryptoCurrencyStatus.Unreachable } - } - - private fun MutableList.addNoAccountWarning(cryptoCurrencyList: List) { - val noAccountNetwork = cryptoCurrencyList.firstOrNull { it.value is CryptoCurrencyStatus.NoAccount } - if (noAccountNetwork != null) { - val amountToCreateAccount = (noAccountNetwork.value as? CryptoCurrencyStatus.NoAccount) - ?.amountToCreateAccount.toString() - add( - element = WalletNotification.Informational.NoAccount( - network = noAccountNetwork.currency.name, - amount = amountToCreateAccount, - symbol = noAccountNetwork.currency.symbol, - ), - ) - } - } - - private companion object { - const val MAX_REMAINING_SIGNATURES_COUNT = 10 - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateCache.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateCache.kt deleted file mode 100644 index 0c81b8e3ee..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateCache.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state.WalletState - -/** - * Wallet state cache. It allows to switch the wallets without additional loading like a PagerView. - * -[REDACTED_AUTHOR] - */ -internal object WalletStateCache { - - private val states = mutableMapOf() - - /** Get state by [userWalletId] */ - fun getState(userWalletId: UserWalletId): WalletState.ContentState? = states[userWalletId] - - /** Add or update [state] by [userWalletId] */ - fun update(userWalletId: UserWalletId, state: WalletState.ContentState) { - states[userWalletId] = state - } - - /** Update all content states */ - fun updateAll(block: (WalletState.ContentState.() -> WalletState.ContentState)) { - states.keys.forEach { - states[it] = block(requireNotNull(states[it])) - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolder.kt deleted file mode 100644 index 59c1f39f13..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolder.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import com.tangem.feature.wallet.presentation.wallet.state.WalletState - -/** - * Wallet state holder - * - * @param initialState initial ui state - * -[REDACTED_AUTHOR] - */ -internal class WalletStateHolder(initialState: WalletState) { - - /** Screen state */ - var uiState: WalletState by mutableStateOf(initialState) - private set - - /** Set screen [state] */ - fun setState(state: WalletState) { - when (state) { - is WalletState.ContentState -> { - cache(state = state) - - uiState = state - } - is WalletState.Initial -> Unit - } - } - - /** Cache [state] [WalletState.ContentState] to [WalletStateCache] */ - private fun cache(state: WalletState.ContentState) { - val selectedWalletIndex = state.walletsListConfig.selectedWalletIndex - val selectedWalletId = state.walletsListConfig.wallets[selectedWalletIndex].id - - WalletStateCache.update(userWalletId = selectedWalletId, state = state) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolderDelegate.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolderDelegate.kt deleted file mode 100644 index 25c3d12698..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletStateHolderDelegate.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class WalletStateHolderDelegate( - private val uiStateHolder: WalletStateHolder, -) : ReadWriteProperty { - - override fun getValue(thisRef: Any?, property: KProperty<*>): WalletState = uiStateHolder.uiState - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: WalletState) { - uiStateHolder.setState(value) - } -} - -internal fun uiStateHolder(initialState: WalletState): ReadWriteProperty { - return WalletStateHolderDelegate(uiStateHolder = WalletStateHolder(initialState = initialState)) -} \ No newline at end of file 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 c0a3cd5476..6c4a46d2ee 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 @@ -1,1440 +1,319 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels -import androidx.lifecycle.* -import androidx.paging.cachedIn -import arrow.core.Either -import arrow.core.getOrElse -import arrow.core.right -import com.tangem.blockchain.blockchains.cardano.CardanoUtils -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.address.AddressType -import com.tangem.common.card.EllipticCurve -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.extensions.isZero -import com.tangem.common.extensions.toMapKey +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.navigation.AppScreen -import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel -import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels -import com.tangem.core.ui.components.transactions.state.TxHistoryState -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.card.SetCardWasScannedUseCase -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.configs.CardConfig -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.settings.* -import com.tangem.domain.tokens.* -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.* -import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent -import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent -import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase +import com.tangem.domain.settings.CanUseBiometryUseCase +import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled +import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase import com.tangem.domain.walletconnect.WalletConnectActions -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UnlockWalletsError -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.usecase.* -import com.tangem.feature.wallet.impl.R +import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.feature.wallet.presentation.wallet.domain.HasSingleWalletSignedHashesUseCase -import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler -import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError -import com.tangem.feature.wallet.presentation.wallet.state.* -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet -import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory -import com.tangem.feature.wallet.presentation.wallet.subscribers.MaybeTokenListFlow -import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.* +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender +import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn -import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.math.BigDecimal +import timber.log.Timber import javax.inject.Inject -import kotlin.properties.Delegates -/** - * Wallet screen view model - * -[REDACTED_AUTHOR] - */ -@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") +@Suppress("LongParameterList") @HiltViewModel internal class WalletViewModel @Inject constructor( - // region Parameters + private val stateHolder: WalletStateController, + private val clickIntents: WalletClickIntents, + private val walletEventSender: WalletEventSender, + private val walletsUpdateActionResolver: WalletsUpdateActionResolver, + private val walletScreenContentLoader: WalletScreenContentLoader, + private val getSelectedWalletUseCase: GetSelectedWalletUseCase, private val getWalletsUseCase: GetWalletsUseCase, - getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val selectWalletUseCase: SelectWalletUseCase, - private val updateWalletUseCase: UpdateWalletUseCase, - private val deleteWalletUseCase: DeleteWalletUseCase, - private val getTokenListUseCase: GetTokenListUseCase, - private val getCardTokensListUseCase: GetCardTokensListUseCase, - private val fetchTokenListUseCase: FetchTokenListUseCase, - private val fetchCardTokenListUseCase: FetchCardTokenListUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, - private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, - private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, - private val getExploreUrlUseCase: GetExploreUrlUseCase, - private val unlockWalletsUseCase: UnlockWalletsUseCase, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, + private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val removeCurrencyUseCase: RemoveCurrencyUseCase, - private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, - private val walletManagersFacade: WalletManagersFacade, - private val reduxStateHolder: ReduxStateHolder, + analyticsEventsHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, - private val analyticsEventsHandler: AnalyticsEventHandler, - private val changeCardAnalyticsContextUseCase: ChangeCardAnalyticsContextUseCase, - private val setCardWasScannedUseCase: SetCardWasScannedUseCase, - private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase, - private val neverToSuggestRateAppUseCase: NeverToSuggestRateAppUseCase, - private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, - private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, - private val isDemoCardUseCase: IsDemoCardUseCase, - private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler, - isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, - isNeedToBackupUseCase: IsNeedToBackupUseCase, - getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, - hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase, - // endregion Parameters -) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents { + private val reduxStateHolder: ReduxStateHolder, + private val screenLifecycleProvider: ScreenLifecycleProvider, + private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, + private val walletDeepLinksHandler: WalletDeepLinksHandler, +) : ViewModel() { - /** Feature router */ - var router: InnerWalletRouter by Delegates.notNull() + val uiState: StateFlow = stateHolder.uiState - private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() + private lateinit var router: InnerWalletRouter + private var walletsUpdateJobHolder: JobHolder = JobHolder() - private val notificationsListFactory = WalletNotificationsListFactory( - isDemoCardUseCase = isDemoCardUseCase, - isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase, - isNeedToBackupUseCase = isNeedToBackupUseCase, - getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase, - hasSingleWalletSignedHashesUseCase = hasSingleWalletSignedHashesUseCase, - clickIntents = this, - ) - - private var isBalanceHidden = true - - private val stateFactory = WalletStateFactory( - currentStateProvider = Provider { uiState }, - currentCardTypeResolverProvider = Provider { - getCardTypeResolver( - index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex, - ) - }, - currentWalletProvider = Provider { - wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex] - }, - appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), - isBalanceHiddenProvider = Provider { isBalanceHidden }, - clickIntents = this, - ) - - /** Screen state */ - var uiState: WalletState by uiStateHolder(initialState = stateFactory.getInitialState()) - - private var wallets: List by Delegates.notNull() - private var singleWalletCryptoCurrencyStatus: CryptoCurrencyStatus? = null - - private val tokensJobHolder = JobHolder() - private val updateWcJobHolder = JobHolder() - private val marketPriceJobHolder = JobHolder() - private val buttonsJobHolder = JobHolder() - private val notificationsJobHolder = JobHolder() - private val refreshContentJobHolder = JobHolder() - private val onWalletChangeJobHolder = JobHolder() - - private val walletsUpdateActionResolver = WalletsUpdateActionResolver( - currentStateProvider = Provider { uiState }, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - ) - - override fun onCreate(owner: LifecycleOwner) { + init { analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened) + suggestToEnableBiometrics() + + subscribeOnWalletsUpdateFlow() + subscribeOnBalanceHiding() + subscribeOnSelectedWalletFlow() + } + + fun setWalletRouter(router: InnerWalletRouter) { + this.router = router + clickIntents.initialize(router, viewModelScope) + } + + fun subscribeToLifecycle(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycle.addObserver(screenLifecycleProvider) + } + + override fun onCleared() { + super.onCleared() + stateHolder.clear() + walletScreenContentLoader.cancelAll() + } + + private fun suggestToEnableBiometrics() { viewModelScope.launch(dispatchers.main) { - delay(timeMillis = 1_800) + withContext(dispatchers.io) { delay(timeMillis = 1_800) } - if (router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase()) { - router.openSaveUserWalletScreen() - } + if (isShowSaveWalletScreenEnabled()) router.openSaveUserWalletScreen() } + } - viewModelScope.launch(dispatchers.io) { + private suspend fun isShowSaveWalletScreenEnabled(): Boolean { + return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() + } + + private fun subscribeOnWalletsUpdateFlow() { + viewModelScope.launch(dispatchers.main) { shouldSaveUserWalletsUseCase() - .flowWithLifecycle(owner.lifecycle) - .collectLatest { - getWalletsUseCase() - .flowWithLifecycle(owner.lifecycle) - .distinctUntilChanged() - .onEach(::updateWallets) - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - } + .conflate() + .distinctUntilChanged() + .collectLatest(::subscribeToUserWalletsUpdates) } + } - getBalanceHidingSettingsUseCase() - .flowWithLifecycle(owner.lifecycle) - .onEach { - isBalanceHidden = it.isBalanceHidden - WalletStateCache.updateAll { copySealed(isBalanceHidden = it.isBalanceHidden) } - uiState = stateFactory.getHiddenBalanceState(isBalanceHidden = it.isBalanceHidden) + private fun subscribeToUserWalletsUpdates(shouldSaveUserWallet: Boolean) { + getWalletsUseCase() + .conflate() + .distinctUntilChanged() + .map { + walletsUpdateActionResolver.resolve( + wallets = it, + currentState = stateHolder.value, + canSaveWallets = shouldSaveUserWallet, + ) } + .onEach(::updateWallets) + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + .saveIn(walletsUpdateJobHolder) + } + + private fun subscribeOnBalanceHiding() { + getBalanceHidingSettingsUseCase() + .conflate() + .distinctUntilChanged() + .onEach { + stateHolder.update(transformer = UpdateBalanceHidingModeTransformer(it.isBalanceHidden)) + } + .flowOn(dispatchers.main) .launchIn(viewModelScope) } - private fun updateWallets(sourceList: List) { - wallets = sourceList + private fun subscribeOnSelectedWalletFlow() { + getSelectedWalletUseCase().onRight { + it + .conflate() + .distinctUntilChanged() + .onEach { selectedWallet -> + if (selectedWallet.isMultiCurrency) { + Timber.d("WalletConnect: initialize and setup networks for ${selectedWallet.walletId}") - if (sourceList.isEmpty()) return + reduxStateHolder.dispatch( + action = WalletConnectActions.New.Initialize(userWallet = selectedWallet), + ) - when (val action = walletsUpdateActionResolver.resolve(sourceList)) { - is WalletsUpdateActionResolver.Action.Initialize -> { - initializeAndLoadState(selectedWalletIndex = action.selectedWalletIndex) + reduxStateHolder.dispatch( + action = WalletConnectActions.New.SetupUserChains(userWallet = selectedWallet), + ) + + selectedWalletAnalyticsSender.send(selectedWallet) + } + + walletDeepLinksHandler.registerForSingleCurrencyWallets( + viewModel = this, + userWallet = selectedWallet, + ) + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + } + } + + private fun updateWallets(action: WalletsUpdateActionResolver.Action) { + when (action) { + is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action) + is WalletsUpdateActionResolver.Action.ReinitializeWallets -> { + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + isRefresh = true, + coroutineScope = viewModelScope, + ) + } + is WalletsUpdateActionResolver.Action.ReinitializeWallet -> reinitializeWallet(action) + is WalletsUpdateActionResolver.Action.AddWallet -> addWallet(action) + is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action) + is WalletsUpdateActionResolver.Action.UnlockWallet -> unlockWallet(action) + is WalletsUpdateActionResolver.Action.UpdateWalletCardCount -> { + stateHolder.update(transformer = UpdateWalletCardsCountTransformer(action.selectedWallet)) } is WalletsUpdateActionResolver.Action.UpdateWalletName -> { - uiState = stateFactory.getStateWithUpdatedWalletName(name = action.name) - } - is WalletsUpdateActionResolver.Action.UnlockWallet -> { - uiState = stateFactory.getUnlockedState(action) - - getContentItemsUpdates(index = action.selectedWalletIndex) - } - is WalletsUpdateActionResolver.Action.DeleteWallet -> { - deleteWalletAndUpdateState(action = action) - } - is WalletsUpdateActionResolver.Action.AddWallet -> { - scrollAndUpdateState(action.selectedWalletIndex) - } - is WalletsUpdateActionResolver.Action.UpdateWalletCardCount -> { - uiState = stateFactory.getStateWithUpdatedWalletCardCount() + stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } is WalletsUpdateActionResolver.Action.Unknown -> Unit } } - private fun initializeAndLoadState(selectedWalletIndex: Int) { - uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = selectedWalletIndex) - - getContentItemsUpdates(index = selectedWalletIndex) - } - - private fun deleteWalletAndUpdateState(action: WalletsUpdateActionResolver.Action.DeleteWallet) { - val cacheState = WalletStateCache.getState(userWalletId = action.selectedWalletId) - if (cacheState != null) { - uiState = stateFactory.getStateWithoutDeletedWallet(cacheState, action) - - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ChangeWallet(action.selectedWalletIndex), - setUiState = { uiState = it }, - ) - getContentItemsUpdates(action.selectedWalletIndex) - } else { - /* It's impossible case because user can delete only visible state, but we support this case */ - scrollAndUpdateState(selectedWalletIndex = action.selectedWalletIndex) - } - } - - private fun scrollAndUpdateState(selectedWalletIndex: Int) { - uiState = stateFactory.getSkeletonState( - wallets = wallets, - selectedWalletIndex = selectedWalletIndex, + private fun initializeWallets(action: WalletsUpdateActionResolver.Action.InitializeWallets) { + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, ) - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ChangeWallet(index = selectedWalletIndex), - setUiState = { uiState = it }, - ) - - getContentItemsUpdates(index = selectedWalletIndex) - } - - override fun onBackClick() { - router.popBackStack() - } - - override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { - val state = uiState as? WalletState.ContentState ?: return - - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)) - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeScanYourCardTapped) - - viewModelScope.launch(dispatchers.io) { - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - - deriveMissingCurrencies( - scanResponse = userWallet.scanResponse, - currencyList = missedAddressCurrencies, - ) { scannedCardResponse -> - updateWalletUseCase( - userWalletId = userWallet.walletId, - update = { it.copy(scanResponse = scannedCardResponse) }, - ) - .onRight { - fetchTokenListUseCase(userWalletId = it.walletId) - } - } - } - } - - @Deprecated("Use DerivePublicKeysUseCase instead") - private fun deriveMissingCurrencies( - scanResponse: ScanResponse, - currencyList: List, - onSuccess: suspend (ScanResponse) -> Unit, - ) { - val config = CardConfig.createConfig(scanResponse.card) - val derivationDataList = currencyList.mapNotNull { - config.primaryCurve(blockchain = Blockchain.fromId(it.network.id.value))?.let { curve -> - getNewDerivations(curve, scanResponse, it) - } - } - - val derivations = buildMap> { - derivationDataList.forEach { - val current = this[it.derivations.first] - if (current != null) { - current.addAll(it.derivations.second) - current.distinct() - } else { - this[it.derivations.first] = it.derivations.second.toMutableList() - } - } - }.ifEmpty { return } - - viewModelScope.launch(dispatchers.io) { - derivePublicKeysUseCase(cardId = null, derivations = derivations) - .onRight { - val newDerivedKeys = it.entries - val oldDerivedKeys = scanResponse.derivedKeys - - val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() - - val updatedDerivedKeys = walletKeys.associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) - val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) - ExtendedPublicKeysMap(oldDerivations + newDerivations) - } - val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys) - - onSuccess(updatedScanResponse) - } - } - } - - private fun getNewDerivations( - curve: EllipticCurve, - scanResponse: ScanResponse, - currency: CryptoCurrency, - ): DerivationData? { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - - val blockchain = Blockchain.fromId(currency.network.id.value) - val supportedCurves = blockchain.getSupportedCurves() - val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) - .takeIf { supportedCurves.contains(curve) } - - val customPath = currency.network.derivationPath.value?.let { - DerivationPath(it) - }.takeIf { supportedCurves.contains(curve) } - - val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() - if (bothCandidates.isEmpty()) return null - - if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) { - currency.network.derivationPath.value?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } - } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys: ExtendedPublicKeysMap = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) - val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - - val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } - if (toDerive.isEmpty()) return null - - return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) - } - - class DerivationData(val derivations: Pair>) - - override fun onScanToUnlockWalletClick() { - val state = uiState as? WalletState.ContentState ?: return - - analyticsEventsHandler.send(event = WalletScreenAnalyticsEvent.MainScreen.WalletUnlockTapped) - - val lockedWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - - viewModelScope.launch(dispatchers.main) { - scanCardToUnlockWalletUseCase(walletId = lockedWallet.walletId) - .onLeft { error -> - when (error) { - ScanCardToUnlockWalletError.WrongCardIsScanned -> { - delay(timeMillis = DELAY_SDK_DIALOG_CLOSE) - - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ShowAlert(state = WalletAlertState.WrongCardIsScanned), - setUiState = { uiState = it }, - ) - } - ScanCardToUnlockWalletError.ManyScanFails -> { - router.openScanFailedDialog() - } - } - } - } - } - - override fun onDetailsClick() = router.openDetailsScreen() - - override fun onBackupCardClick() { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeBackupYourWalletTapped) - reduxStateHolder.dispatch( - LegacyAction.StartOnboardingProcess( - scanResponse = getSelectedWallet().scanResponse, - canSkipBackup = false, + stateHolder.update( + transformer = InitializeWalletsTransformer( + selectedWalletIndex = action.selectedWalletIndex, + selectedWallet = action.selectedWallet, + wallets = action.wallets, + clickIntents = clickIntents, ), ) - router.openOnboardingScreen() + + viewModelScope.launch(dispatchers.main) { + if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) { + withContext(dispatchers.io) { + delay(timeMillis = 1_800) + } + + walletEventSender.send( + event = WalletEvent.DemonstrateWalletsScrollPreview( + direction = if (action.selectedWalletIndex == action.wallets.lastIndex) { + Direction.RIGHT + } else { + Direction.LEFT + }, + ), + ) + } + } } - override fun onSignedHashesNotificationCloseClick() { - val state = uiState as? WalletState.ContentState ?: return + private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) { viewModelScope.launch(dispatchers.main) { - setCardWasScannedUseCase( - cardId = getWallet(index = state.walletsListConfig.selectedWalletIndex).cardId, + walletScreenContentLoader.cancel(action.prevWalletId) + + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, + ) + + stateHolder.update( + ReinitializeWalletTransformer(userWallet = action.selectedWallet, clickIntents = clickIntents), ) } } - override fun onLikeAppClick() { - analyticsEventsHandler.send( - WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked), - ) - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.RateApp( - onDismissClick = { - viewModelScope.launch(dispatchers.main) { - neverToSuggestRateAppUseCase() - } - }, + private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { + viewModelScope.launch(dispatchers.main) { + stateHolder.update( + AddWalletTransformer( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + ), + ) + + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, + ) + + withContext(dispatchers.io) { delay(timeMillis = 700) } + + scrollToWallet(index = action.selectedWalletIndex) + } + } + + private fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { + viewModelScope.launch(dispatchers.main) { + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, + ) + + scrollToWallet(index = action.selectedWalletIndex) + + withContext(dispatchers.io) { delay(timeMillis = 700) } + + stateHolder.update( + DeleteWalletTransformer( + selectedWalletIndex = action.selectedWalletIndex, + deletedWalletId = action.deletedWalletId, + ), + ) + } + } + + private fun unlockWallet(action: WalletsUpdateActionResolver.Action.UnlockWallet) { + viewModelScope.launch(dispatchers.main) { + withContext(dispatchers.io) { delay(timeMillis = 700) } + + stateHolder.update( + transformer = UnlockWalletTransformer( + unlockedWallets = action.unlockedWallets, + clickIntents = clickIntents, + ), + ) + + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, + ) + } + } + + private fun scrollToWallet(index: Int) { + stateHolder.update( + ScrollToWalletTransformer( + index = index, + currentStateProvider = Provider(action = stateHolder::value), + stateUpdater = { newState -> stateHolder.update { newState } }, ), - setUiState = { uiState = it }, ) } - - override fun onDislikeAppClick() { - analyticsEventsHandler.send( - WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked), - ) - viewModelScope.launch(dispatchers.main) { - neverToSuggestRateAppUseCase() - - reduxStateHolder.dispatch(LegacyAction.SendEmailRateCanBeBetter) - } - } - - override fun onCloseRateAppNotificationClick() { - analyticsEventsHandler.send( - WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed), - ) - viewModelScope.launch(dispatchers.main) { - remindToRateAppLaterUseCase() - } - } - - override fun onWalletChange(index: Int) { - val state = uiState as? WalletState.ContentState ?: return - if (state.walletsListConfig.selectedWalletIndex == index) return - - changeCardAnalyticsContextUseCase(scanResponse = getWallet(index).scanResponse) - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.WalletSwipe) - - // Reset the job to avoid a redundant state updating - onWalletChangeJobHolder.update(null) - - /* - * When wallet is changed it's necessary to stop the last jobs. - * If jobs aren't stopped and wallet is changed then it will update state for the prev wallet. - */ - tokensJobHolder.update(job = null) - updateWcJobHolder.update(job = null) - marketPriceJobHolder.update(job = null) - buttonsJobHolder.update(job = null) - notificationsJobHolder.update(job = null) - refreshContentJobHolder.update(job = null) - - viewModelScope.launch(dispatchers.main) { - val userWallet = state.walletsListConfig.wallets[index] - - withContext(dispatchers.io) { - selectWalletUseCase(userWallet.id) - } - - val cacheState = WalletStateCache.getState(userWalletId = userWallet.id) - if (cacheState != null && cacheState !is WalletLockedState) { - uiState = cacheState.copySealed( - walletsListConfig = state.walletsListConfig.copy( - selectedWalletIndex = index, - wallets = state.walletsListConfig.wallets - .mapIndexed { mapIndex, currentWallet -> - val cacheWallet = cacheState.walletsListConfig.wallets.getOrNull(mapIndex) - - if (currentWallet is WalletCardState.Loading && cacheWallet != null && - cacheWallet.isLoaded() - ) { - cacheWallet - } else { - currentWallet - } - } - .toImmutableList(), - ), - pullToRefreshConfig = cacheState.pullToRefreshConfig.copy(isRefreshing = false), - ) - - getContentItemsUpdates(index) - } else { - initializeAndLoadState(selectedWalletIndex = index) - } - } - .saveIn(onWalletChangeJobHolder) - } - - private fun WalletCardState.isLoaded(): Boolean { - return this !is WalletCardState.Loading && this !is WalletCardState.LockedContent - } - - override fun onRefreshSwipe() { - val selectedWalletIndex = (uiState as? WalletState.ContentState) - ?.walletsListConfig - ?.selectedWalletIndex - ?: return - - when (uiState) { - is WalletMultiCurrencyState.Content -> { - analyticsEventsHandler.send(PortfolioEvent.Refreshed) - refreshMultiCurrencyContent(selectedWalletIndex) - } - is WalletSingleCurrencyState.Content -> { - analyticsEventsHandler.send(PortfolioEvent.Refreshed) - refreshSingleCurrencyContent(selectedWalletIndex) - } - is WalletState.Initial, - is WalletMultiCurrencyState.Locked, - is WalletSingleCurrencyState.Locked, - -> Unit - } - } - - private fun refreshMultiCurrencyContent(walletIndex: Int) { - uiState = stateFactory.getRefreshingState() - - viewModelScope.launch(dispatchers.main) { - val wallet = getWallet(walletIndex) - - val maybeFetchResult = if (wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { - fetchCardTokenListUseCase(userWalletId = wallet.walletId, refresh = true) - } else { - fetchTokenListUseCase(userWalletId = wallet.walletId, refresh = true) - } - - maybeFetchResult.onLeft { uiState = stateFactory.getStateByTokenListError(it) } - - uiState = stateFactory.getRefreshedState() - }.saveIn(refreshContentJobHolder) - } - - override fun onOrganizeTokensClick() { - analyticsEventsHandler.send(PortfolioEvent.OrganizeTokens) - - val state = requireNotNull(uiState as? WalletState.ContentState) - val index = state.walletsListConfig.selectedWalletIndex - val walletId = state.walletsListConfig.wallets[index].id - - router.openOrganizeTokensScreen(walletId) - } - - override fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val state = uiState as? WalletState.ContentState ?: return - - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrencyStatus.currency.symbol), - ) - - showErrorIfDemoModeOrElse { - reduxStateHolder.dispatch( - TradeCryptoAction.Buy( - userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex), - cryptoCurrencyStatus = cryptoCurrencyStatus, - appCurrencyCode = selectedAppCurrencyFlow.value.code, - ), - ) - } - } - - override fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol), - ) - - reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrencyStatus.currency)) - } - - override fun onSingleCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus?) { - val state = uiState as? WalletState.ContentState ?: return - - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - val coinStatus = if (userWallet.isMultiCurrency) cryptoCurrencyStatus else singleWalletCryptoCurrencyStatus - coinStatus ?: return - - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonSend(coinStatus.currency.symbol), - ) - - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, coinStatus).getOrNull() - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = coinStatus, - feeCurrencyStatus = maybeFeeCurrencyStatus, - ), - ) - } - } - - override fun onMultiCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val state = uiState as? WalletState.ContentState ?: return - - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol), - ) - - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, cryptoCurrencyStatus).getOrNull() - when (cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> { - uiState = stateFactory.getStateWithClosedBottomSheet() - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - feeCurrencyStatus = maybeFeeCurrencyStatus, - ), - ) - } - is CryptoCurrency.Token -> sendToken(userWallet, cryptoCurrencyStatus, maybeFeeCurrencyStatus) - } - } - } - - private fun sendToken( - userWallet: UserWallet, - cryptoCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - ) { - viewModelScope.launch(dispatchers.io) { - getNetworkCoinStatusUseCase( - userWalletId = userWallet.walletId, - networkId = cryptoCurrencyStatus.currency.network.id, - derivationPath = cryptoCurrencyStatus.currency.network.derivationPath, - isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .take(count = 1) - .collectLatest { - it.onRight { coinStatus -> - uiState = stateFactory.getStateWithClosedBottomSheet() - reduxStateHolder.dispatchWithMain( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = requireNotNull(cryptoCurrencyStatus.currency as? CryptoCurrency.Token), - tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, - coinFiatRate = coinStatus.value.fiatRate, - feeCurrencyStatus = feeCurrencyStatus, - ), - ) - } - } - } - } - - override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol), - ) - - viewModelScope.launch(dispatchers.io) { - analyticsEventsHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened) - - val currency = cryptoCurrencyStatus.currency - uiState = stateFactory.getStateWithOpenWalletBottomSheet( - content = TokenReceiveBottomSheetConfig( - name = currency.name, - symbol = currency.symbol, - network = currency.network.name, - addresses = cryptoCurrencyStatus.value.networkAddress - ?.availableAddresses - ?.mapToAddressModels(currency) - .orEmpty() - .toImmutableList(), - onCopyClick = { - analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol)) - }, - onShareClick = { - analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonShareAddress(currency.symbol)) - }, - ), - ) - } - } - - override fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val state = uiState as? WalletState.ContentState ?: return - - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonCopyAddress(cryptoCurrencyStatus.currency.symbol), - ) - - viewModelScope.launch(dispatchers.main) { - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - - val defaultAddress = walletManagersFacade.getAddress( - userWalletId = userWallet.walletId, - network = cryptoCurrencyStatus.currency.network, - ).find { it.type == AddressType.Default } - - defaultAddress?.value?.let { address -> - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.CopyAddress( - address = address, - toast = resourceReference(R.string.wallet_notification_address_copied), - ), - setUiState = { uiState = it }, - ) - } - } - } - - override fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrencyStatus.currency.symbol), - ) - - showErrorIfDemoModeOrElse { - reduxStateHolder.dispatch( - action = TradeCryptoAction.Sell( - cryptoCurrencyStatus = cryptoCurrencyStatus, - appCurrencyCode = selectedAppCurrencyFlow.value.code, - ), - ) - } - } - - override fun onManageTokensClick() { - analyticsEventsHandler.send(PortfolioEvent.ButtonManageTokens) - reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess) - router.openManageTokensScreen() - } - - override fun onReloadClick() { - val selectedWalletIndex = (uiState as? WalletSingleCurrencyState) - ?.walletsListConfig - ?.selectedWalletIndex - ?: return - - refreshSingleCurrencyContent(selectedWalletIndex) - } - - // FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary - // currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged() - private fun refreshSingleCurrencyContent(walletIndex: Int) { - uiState = stateFactory.getRefreshingState() - val wallet = getWallet(walletIndex) - - viewModelScope.launch(dispatchers.main) { - singleWalletCryptoCurrencyStatus?.let { - updateButtons(userWallet = wallet, currencyStatus = it) - } - val result = fetchCurrencyStatusUseCase(wallet.walletId, refresh = true) - - uiState = stateFactory.getRefreshedState() - uiState = result.fold(stateFactory::getStateByCurrencyStatusError) { uiState } - - singleWalletCryptoCurrencyStatus?.let { - val singleCurrencyState = uiState as WalletSingleCurrencyState - if (singleCurrencyState.txHistoryState !is TxHistoryState.Content) { - // show loading indicator while refreshing in non content state - uiState = stateFactory.getLoadingTxHistoryState( - itemsCountEither = 1.right(), - pendingTransactions = it.value.pendingTransactions, - ) - } - updateTxHistory(userWalletId = wallet.walletId, currencyStatus = it, refresh = true) - } - }.saveIn(refreshContentJobHolder) - } - - override fun onExploreClick() { - showErrorIfDemoModeOrElse(action = ::openExplorer) - } - - private fun openExplorer() { - val state = uiState as? WalletState.ContentState ?: return - val currencyStatus = singleWalletCryptoCurrencyStatus ?: return - val currency = currencyStatus.currency - - viewModelScope.launch(dispatchers.main) { - val userWalletId = getWallet(state.walletsListConfig.selectedWalletIndex).walletId - - when (val addresses = currencyStatus.value.networkAddress) { - is NetworkAddress.Selectable -> { - uiState = stateFactory.getStateWithOpenWalletBottomSheet( - ChooseAddressBottomSheetConfig( - addressModels = addresses.availableAddresses - .mapToAddressModels(currency) - .toImmutableList(), - onClick = { - onAddressTypeSelected( - userWalletId = userWalletId, - currency = currency, - addressModel = it, - ) - }, - ), - ) - } - is NetworkAddress.Single -> { - router.openUrl( - url = getExploreUrlUseCase( - userWalletId = userWalletId, - currency = currency, - addressType = AddressType.Default, - ), - ) - } - null -> Unit - } - } - } - - private fun onAddressTypeSelected( - userWalletId: UserWalletId, - currency: CryptoCurrency, - addressModel: AddressModel, - ) { - viewModelScope.launch(dispatchers.main) { - router.openUrl( - url = getExploreUrlUseCase( - userWalletId = userWalletId, - currency = currency, - addressType = AddressType.valueOf(addressModel.type.name), - ), - ) - uiState = stateFactory.getStateWithClosedBottomSheet() - } - } - - private fun showErrorIfDemoModeOrElse(action: () -> Unit) { - val state = uiState as? WalletState.ContentState ?: return - val cardId = getWallet(index = state.walletsListConfig.selectedWalletIndex).cardId - - if (isDemoCardUseCase(cardId = cardId)) { - uiState = stateFactory.getStateWithClosedBottomSheet() - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ShowError( - text = resourceReference(id = R.string.alert_demo_feature_disabled), - ), - setUiState = { uiState = it }, - ) - } else { - action() - } - } - - override fun onUnlockWalletClick() { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeWalletLocked) - - viewModelScope.launch(dispatchers.main) { - unlockWalletsUseCase(throwIfNotAllWalletsUnlocked = true) - .onLeft(::handleUnlockWalletsError) - } - } - - private fun handleUnlockWalletsError(error: UnlockWalletsError) { - val event = when (error) { - is UnlockWalletsError.DataError, - is UnlockWalletsError.UnableToUnlockWallets, - -> WalletEvent.ShowToast(resourceReference(R.string.user_wallet_list_error_unable_to_unlock)) - is UnlockWalletsError.NoUserWalletSelected, - is UnlockWalletsError.NotAllUserWalletsUnlocked, - -> WalletEvent.ShowAlert(WalletAlertState.RescanWallets) - } - - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = event, - setUiState = { uiState = it }, - ) - } - - override fun onUnlockWalletNotificationClick() { - val state = requireNotNull(uiState as? WalletLockedState) { - "Impossible to unlock wallet if state isn't WalletLockedState" - } - - uiState = stateFactory.getStateWithOpenWalletBottomSheet( - content = when (state) { - is WalletMultiCurrencyState.Locked -> state.bottomSheetConfig.content - is WalletSingleCurrencyState.Locked -> state.bottomSheetConfig.content - }, - ) - } - - override fun onTokenItemClick(currency: CryptoCurrency) { - analyticsEventsHandler.send(PortfolioEvent.TokenTapped) - router.openTokenDetails(getSelectedWallet().walletId, currency) - } - - override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val userWallet = getSelectedWallet() - viewModelScope.launch(dispatchers.io) { - getCryptoCurrencyActionsUseCase( - userWallet = userWallet, - cryptoCurrencyStatus = cryptoCurrencyStatus, - ) - .take(count = 1) - .collectLatest { - uiState = stateFactory.getStateWithTokenActionBottomSheet(it) - } - } - } - - override fun onRenameBeforeConfirmationClick(userWalletId: UserWalletId) { - val state = uiState as? WalletState.ContentState ?: return - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ShowAlert( - state = WalletAlertState.RenameWalletAlert( - text = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex].title, - onConfirmClick = { onRenameAfterConfirmationClick(userWalletId, it) }, - ), - ), - setUiState = { uiState = it }, - ) - } - - override fun onRenameAfterConfirmationClick(userWalletId: UserWalletId, name: String) { - viewModelScope.launch(dispatchers.io) { - updateWalletUseCase(userWalletId = userWalletId, update = { it.copy(name = name) }) - } - } - - override fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId) { - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ShowAlert( - state = WalletAlertState.RemoveWalletAlert( - onConfirmClick = { onDeleteAfterConfirmationClick(userWalletId) }, - ), - ), - setUiState = { uiState = it }, - ) - } - - override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { - val state = uiState as? WalletState.ContentState ?: return - viewModelScope.launch(dispatchers.io) { - deleteWalletUseCase(userWalletId) - - popBackIfAllWalletsIsLocked(wallets = state.walletsListConfig.wallets) - } - } - - private fun popBackIfAllWalletsIsLocked(wallets: List) { - val unlockedWallet = wallets.count { it !is WalletCardState.LockedContent } - - if (unlockedWallet == 1) { - router.popBackStack( - screen = if (wallets.size > 1) AppScreen.Welcome else AppScreen.Home, - ) - } - } - - override fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val state = uiState as? WalletState.ContentState ?: return - val userWallet = getWallet(state.walletsListConfig.selectedWalletIndex) - viewModelScope.launch(dispatchers.io) { - removeCurrencyUseCase(userWallet.walletId, cryptoCurrencyStatus.currency) - .fold( - ifLeft = { showToast(resourceReference(R.string.common_error)) }, - ifRight = { uiState = stateFactory.getStateWithClosedBottomSheet() }, - ) - } - } - - override fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol), - ) - - viewModelScope.launch(dispatchers.main) { - val state = uiState as? WalletState.ContentState ?: return@launch - val userWallet = getWallet(state.walletsListConfig.selectedWalletIndex) - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = getHideTokeAlert(userWallet.walletId, cryptoCurrencyStatus), - setUiState = { uiState = it }, - ) - } - } - - override fun onDismissBottomSheet() { - uiState = stateFactory.getStateWithClosedBottomSheet() - } - - override fun onTransactionClick(txHash: String) { - singleWalletCryptoCurrencyStatus?.let { currencyStatus -> - router.openUrl( - url = getExplorerTransactionUrlUseCase( - txHash = txHash, - networkId = currencyStatus.currency.network.id, - ), - ) - } - } - - private suspend fun getHideTokeAlert( - userWalletId: UserWalletId, - cryptoCurrencyStatus: CryptoCurrencyStatus, - ): WalletEvent.ShowAlert { - val currency = cryptoCurrencyStatus.currency - return if (currency is CryptoCurrency.Coin && !isCryptoCurrencyCoinCouldHide(userWalletId, currency)) { - WalletEvent.ShowAlert( - state = WalletAlertState.DefaultAlert( - title = resourceReference( - id = R.string.token_details_unable_hide_alert_title, - formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)), - ), - message = resourceReference( - id = R.string.token_details_unable_hide_alert_message, - formatArgs = WrappedList( - listOf( - cryptoCurrencyStatus.currency.name, - cryptoCurrencyStatus.currency.network.name, - ), - ), - ), - onConfirmClick = null, - ), - ) - } else { - WalletEvent.ShowAlert( - state = WalletAlertState.DefaultAlert( - title = resourceReference( - id = R.string.token_details_hide_alert_title, - formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)), - ), - message = resourceReference(R.string.token_details_hide_alert_message), - onConfirmClick = { onPerformHideToken(cryptoCurrencyStatus) }, - ), - ) - } - } - - private fun showToast(message: TextReference) { - uiState = stateFactory.getStateAndTriggerEvent( - state = uiState, - event = WalletEvent.ShowToast(message), - setUiState = { uiState = it }, - ) - } - - private fun getContentItemsUpdates(index: Int) { - /* - * When wallet is changed it's necessary to stop the last jobs. - * If jobs aren't stopped and wallet is changed then it will update state for the prev wallet. - */ - tokensJobHolder.update(job = null) - updateWcJobHolder.update(job = null) - marketPriceJobHolder.update(job = null) - buttonsJobHolder.update(job = null) - notificationsJobHolder.update(job = null) - refreshContentJobHolder.update(job = null) - - val wallet = getWallet(index) - - when { - wallet.isLocked -> { - uiState = stateFactory.getLockedState() - } - wallet.isMultiCurrency -> getMultiCurrencyContent(wallet, index) - wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> getSingleCurrencyWithTokenContent(index) - !wallet.isMultiCurrency -> getSingleCurrencyContent(index) - } - } - - private fun getMultiCurrencyContent(wallet: UserWallet, walletIndex: Int) { - val state = requireNotNull(uiState as? WalletMultiCurrencyState) { - "Impossible to get a token list updates if state isn't WalletMultiCurrencyState" - } - - val tokenListFlow = getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id) - .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - - initAndSetupWc(tokenListFlow, wallet) - - tokenListFlow - .conflate() - .distinctUntilChanged() - .onEach { maybeTokenList -> - uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet)) - - maybeTokenList.onRight { - analyticsEventsHandler.sendBalanceLoadedEvent(it) - checkMultiWalletWithFunds(it) - } - - updateNotifications( - index = walletIndex, - tokenList = maybeTokenList.fold(ifLeft = { null }, ifRight = { it }), - ) - } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(tokensJobHolder) - } - - private suspend fun checkMultiWalletWithFunds(tokenList: TokenList) { - val hasNonZeroWallets = when (tokenList) { - is TokenList.GroupedByNetwork -> { - tokenList.groups - .flatMap(NetworkGroup::currencies) - .hasNonZeroWallets() - } - is TokenList.Ungrouped -> tokenList.currencies.hasNonZeroWallets() - is TokenList.Empty -> false - } - - if (hasNonZeroWallets) { - setWalletWithFundsFoundUseCase() - } - } - - private fun initAndSetupWc(tokenListFlow: MaybeTokenListFlow, wallet: UserWallet) { - viewModelScope - .launch(dispatchers.main) { - initWalletConnectForWallet(wallet) - - tokenListFlow - .filterLoadedTokenList() - .take(count = 1) - .collect { setupWalletConnectOnWallet(wallet) } - } - .saveIn(updateWcJobHolder) - } - - private fun initWalletConnectForWallet(userWallet: UserWallet) { - reduxStateHolder.dispatch( - action = WalletConnectActions.New.Initialize(userWallet = userWallet), - ) - } - - private suspend fun setupWalletConnectOnWallet(userWallet: UserWallet) { - reduxStateHolder.dispatchWithMain( - action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet), - ) - } - - private fun List.isAllCurrenciesLoaded(): Boolean { - return !this.any { it.value is CryptoCurrencyStatus.Loading } - } - - private fun MaybeTokenListFlow.filterLoadedTokenList(): MaybeTokenListFlow { - return filter { either -> - either.fold( - ifRight = { list -> - when (list) { - is TokenList.Ungrouped -> { - list.currencies.isAllCurrenciesLoaded() - } - is TokenList.GroupedByNetwork -> { - list.groups.flatMap(NetworkGroup::currencies).isAllCurrenciesLoaded() - } - else -> false - } - }, - ifLeft = { false }, - ) - } - } - - private fun List.hasNonZeroWallets(): Boolean { - return any { - val amount = it.value.amount ?: return@any false - !amount.isZero() - } - } - - private fun Either.getTokenListWithWallet( - userWallet: UserWallet, - ): Either { - return this.map { - TokenListWithWallet(it, userWallet) - } - } - - private fun getSingleCurrencyContent(index: Int) { - val wallet = getWallet(index) - getPrimaryCurrencyStatusUpdatesUseCase(wallet.walletId) - .conflate() - .distinctUntilChanged() - .onEach { maybeCryptoCurrencyStatus -> - uiState = stateFactory.getSingleCurrencyLoadedBalanceState(maybeCryptoCurrencyStatus) - - maybeCryptoCurrencyStatus.onRight { status -> - val fiatAmount = status.value.fiatAmount - - val cardBalanceState = when (status.value) { - is CryptoCurrencyStatus.Loaded, - is CryptoCurrencyStatus.NoAccount, - is CryptoCurrencyStatus.NoAmount, - -> { - when { - fiatAmount == null -> null - fiatAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty - else -> AnalyticsParam.CardBalanceState.Full - } - } - is CryptoCurrencyStatus.NoQuote -> AnalyticsParam.CardBalanceState.NoRate - is CryptoCurrencyStatus.Unreachable, - -> AnalyticsParam.CardBalanceState.BlockchainError - is CryptoCurrencyStatus.MissedDerivation, - is CryptoCurrencyStatus.Loading, - is CryptoCurrencyStatus.Custom, - -> null - } - - cardBalanceState?.let { - analyticsEventsHandler.send( - event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it), - ) - } - - singleWalletCryptoCurrencyStatus = status - - if (status.value.amount?.isZero() == false) { - setWalletWithFundsFoundUseCase() - } - - updateNotifications(index) - updateButtons(userWallet = wallet, currencyStatus = status) - updateTxHistory(userWalletId = wallet.walletId, currencyStatus = status, refresh = false) - } - } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(marketPriceJobHolder) - } - - private fun getSingleCurrencyWithTokenContent(walletIndex: Int) { - val state = requireNotNull(uiState as? WalletMultiCurrencyState) { - "Impossible to get a token list updates if state isn't WalletMultiCurrencyState" - } - - val wallet = getWallet(walletIndex) - - getCardTokensListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id) - .conflate() - .distinctUntilChanged() - .onEach { maybeTokenList -> - uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet)) - - maybeTokenList.onRight { tokenList -> - analyticsEventsHandler.sendBalanceLoadedEvent(tokenList) - checkMultiWalletWithFunds(tokenList) - } - - updateNotifications( - index = walletIndex, - tokenList = maybeTokenList.fold(ifLeft = { null }, ifRight = { it }), - ) - } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(tokensJobHolder) - } - - private fun AnalyticsEventHandler.sendBalanceLoadedEvent(tokenList: TokenList) { - val cardBalanceState = when (val fiatBalance = tokenList.totalFiatBalance) { - is TokenList.FiatBalance.Failed -> { - val currenciesStatuses = when (tokenList) { - is TokenList.Empty -> emptyList() - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - } - - when { - currenciesStatuses.isEmpty() -> AnalyticsParam.CardBalanceState.Empty - currenciesStatuses.any { it.value is CryptoCurrencyStatus.NoQuote } -> { - AnalyticsParam.CardBalanceState.NoRate - } - else -> AnalyticsParam.CardBalanceState.BlockchainError - } - } - is TokenList.FiatBalance.Loaded -> { - if (fiatBalance.amount > BigDecimal.ZERO) { - AnalyticsParam.CardBalanceState.Full - } else if (fiatBalance.amount.isZero()) { - AnalyticsParam.CardBalanceState.Empty - } else { - null - } - } - TokenList.FiatBalance.Loading -> null - } - - cardBalanceState?.let { - send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it)) - } - } - - private fun updateTxHistory(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, refresh: Boolean) { - viewModelScope.launch(dispatchers.io) { - val txHistoryItemsCountEither = txHistoryItemsCountUseCase( - userWalletId = userWalletId, - currency = currencyStatus.currency, - ) - - uiState = stateFactory.getLoadingTxHistoryState( - itemsCountEither = txHistoryItemsCountEither, - pendingTransactions = currencyStatus.value.pendingTransactions, - ) - - txHistoryItemsCountEither.onRight { - uiState = stateFactory.getLoadedTxHistoryState( - txHistoryEither = txHistoryItemsUseCase( - userWalletId = userWalletId, - currency = currencyStatus.currency, - refresh = refresh, - ).map { - it.cachedIn(viewModelScope) - }, - ) - } - } - } - - private fun updateButtons(userWallet: UserWallet, currencyStatus: CryptoCurrencyStatus) { - getCryptoCurrencyActionsUseCase( - userWallet = userWallet, - cryptoCurrencyStatus = currencyStatus, - ) - .conflate() - .distinctUntilChanged() - .onEach { uiState = stateFactory.getSingleCurrencyManageButtonsState(actionsState = it) } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(buttonsJobHolder) - } - - private fun updateNotifications(index: Int, tokenList: TokenList? = null) { - notificationsListFactory.create( - selectedWallet = getWallet(index), - cryptoCurrencyList = if (tokenList != null) { - when (tokenList) { - is TokenList.GroupedByNetwork -> { - tokenList.groups - .flatMap(NetworkGroup::currencies) - } - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } - } else { - listOfNotNull(singleWalletCryptoCurrencyStatus) - }, - ) - .conflate() - .distinctUntilChanged() - .onEach { uiState = stateFactory.getStateByNotifications(notifications = it) } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(notificationsJobHolder) - } - - private fun createSelectedAppCurrencyFlow(): StateFlow { - return getSelectedAppCurrencyUseCase() - .map { maybeAppCurrency -> - maybeAppCurrency.getOrElse { AppCurrency.Default } - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = AppCurrency.Default, - ) - } - - private fun getWallet(index: Int): UserWallet { - return requireNotNull( - value = wallets.getOrNull(index), - lazyMessage = { "WalletsList doesn't contain element with index = $index" }, - ) - } - - private fun getSelectedWallet(): UserWallet { - val state = uiState as? WalletState.ContentState - ?: error("Unable to get selected user wallet") - - return getWallet(state.walletsListConfig.selectedWalletIndex) - } - - private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt deleted file mode 100644 index 418da4487e..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt +++ /dev/null @@ -1,319 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.settings.CanUseBiometryUseCase -import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled -import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase -import com.tangem.domain.walletconnect.WalletConnectActions -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase -import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase -import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler -import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender -import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview.Direction -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.* -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender -import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2 -import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import timber.log.Timber -import javax.inject.Inject - -@Suppress("LongParameterList") -@HiltViewModel -internal class WalletViewModelV2 @Inject constructor( - private val stateHolder: WalletStateController, - private val clickIntents: WalletClickIntentsV2, - private val walletEventSender: WalletEventSender, - private val walletsUpdateActionResolver: WalletsUpdateActionResolverV2, - private val walletScreenContentLoader: WalletScreenContentLoader, - private val getSelectedWalletUseCase: GetSelectedWalletUseCase, - private val getWalletsUseCase: GetWalletsUseCase, - private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, - private val canUseBiometryUseCase: CanUseBiometryUseCase, - private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, - private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled, - private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - analyticsEventsHandler: AnalyticsEventHandler, - private val dispatchers: CoroutineDispatcherProvider, - private val reduxStateHolder: ReduxStateHolder, - private val screenLifecycleProvider: ScreenLifecycleProvider, - private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, - private val walletDeepLinksHandler: WalletDeepLinksHandler, -) : ViewModel() { - - val uiState: StateFlow = stateHolder.uiState - - private lateinit var router: InnerWalletRouter - private var walletsUpdateJobHolder: JobHolder = JobHolder() - - init { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened) - - suggestToEnableBiometrics() - - subscribeOnWalletsUpdateFlow() - subscribeOnBalanceHiding() - subscribeOnSelectedWalletFlow() - } - - fun setWalletRouter(router: InnerWalletRouter) { - this.router = router - clickIntents.initialize(router, viewModelScope) - } - - fun subscribeToLifecycle(lifecycleOwner: LifecycleOwner) { - lifecycleOwner.lifecycle.addObserver(screenLifecycleProvider) - } - - override fun onCleared() { - super.onCleared() - stateHolder.clear() - walletScreenContentLoader.cancelAll() - } - - private fun suggestToEnableBiometrics() { - viewModelScope.launch(dispatchers.main) { - withContext(dispatchers.io) { delay(timeMillis = 1_800) } - - if (isShowSaveWalletScreenEnabled()) router.openSaveUserWalletScreen() - } - } - - private suspend fun isShowSaveWalletScreenEnabled(): Boolean { - return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() - } - - private fun subscribeOnWalletsUpdateFlow() { - viewModelScope.launch(dispatchers.main) { - shouldSaveUserWalletsUseCase() - .conflate() - .distinctUntilChanged() - .collectLatest(::subscribeToUserWalletsUpdates) - } - } - - private fun subscribeToUserWalletsUpdates(shouldSaveUserWallet: Boolean) { - getWalletsUseCase() - .conflate() - .distinctUntilChanged() - .map { - walletsUpdateActionResolver.resolve( - wallets = it, - currentState = stateHolder.value, - canSaveWallets = shouldSaveUserWallet, - ) - } - .onEach(::updateWallets) - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - .saveIn(walletsUpdateJobHolder) - } - - private fun subscribeOnBalanceHiding() { - getBalanceHidingSettingsUseCase() - .conflate() - .distinctUntilChanged() - .onEach { - stateHolder.update(transformer = UpdateBalanceHidingModeTransformer(it.isBalanceHidden)) - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - } - - private fun subscribeOnSelectedWalletFlow() { - getSelectedWalletUseCase().onRight { - it - .conflate() - .distinctUntilChanged() - .onEach { selectedWallet -> - if (selectedWallet.isMultiCurrency) { - Timber.d("WalletConnect: initialize and setup networks for ${selectedWallet.walletId}") - - reduxStateHolder.dispatch( - action = WalletConnectActions.New.Initialize(userWallet = selectedWallet), - ) - - reduxStateHolder.dispatch( - action = WalletConnectActions.New.SetupUserChains(userWallet = selectedWallet), - ) - - selectedWalletAnalyticsSender.send(selectedWallet) - } - - walletDeepLinksHandler.registerForSingleCurrencyWallets( - viewModel = this, - userWallet = selectedWallet, - ) - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - } - } - - private fun updateWallets(action: WalletsUpdateActionResolverV2.Action) { - when (action) { - is WalletsUpdateActionResolverV2.Action.InitializeWallets -> initializeWallets(action) - is WalletsUpdateActionResolverV2.Action.ReinitializeWallets -> { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - isRefresh = true, - coroutineScope = viewModelScope, - ) - } - is WalletsUpdateActionResolverV2.Action.ReinitializeWallet -> reinitializeWallet(action) - is WalletsUpdateActionResolverV2.Action.AddWallet -> addWallet(action) - is WalletsUpdateActionResolverV2.Action.DeleteWallet -> deleteWallet(action) - is WalletsUpdateActionResolverV2.Action.UnlockWallet -> unlockWallet(action) - is WalletsUpdateActionResolverV2.Action.UpdateWalletCardCount -> { - stateHolder.update(transformer = UpdateWalletCardsCountTransformer(action.selectedWallet)) - } - is WalletsUpdateActionResolverV2.Action.UpdateWalletName -> { - stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) - } - is WalletsUpdateActionResolverV2.Action.Unknown -> Unit - } - } - - private fun initializeWallets(action: WalletsUpdateActionResolverV2.Action.InitializeWallets) { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - - stateHolder.update( - transformer = InitializeWalletsTransformer( - selectedWalletIndex = action.selectedWalletIndex, - selectedWallet = action.selectedWallet, - wallets = action.wallets, - clickIntents = clickIntents, - ), - ) - - viewModelScope.launch(dispatchers.main) { - if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) { - withContext(dispatchers.io) { - delay(timeMillis = 1_800) - } - - walletEventSender.send( - event = WalletEvent.DemonstrateWalletsScrollPreview( - direction = if (action.selectedWalletIndex == action.wallets.lastIndex) { - Direction.RIGHT - } else { - Direction.LEFT - }, - ), - ) - } - } - } - - private fun reinitializeWallet(action: WalletsUpdateActionResolverV2.Action.ReinitializeWallet) { - viewModelScope.launch(dispatchers.main) { - walletScreenContentLoader.cancel(action.prevWalletId) - - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - - stateHolder.update( - ReinitializeWalletTransformer(userWallet = action.selectedWallet, clickIntents = clickIntents), - ) - } - } - - private fun addWallet(action: WalletsUpdateActionResolverV2.Action.AddWallet) { - viewModelScope.launch(dispatchers.main) { - stateHolder.update( - AddWalletTransformer( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - ), - ) - - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - - withContext(dispatchers.io) { delay(timeMillis = 700) } - - scrollToWallet(index = action.selectedWalletIndex) - } - } - - private fun deleteWallet(action: WalletsUpdateActionResolverV2.Action.DeleteWallet) { - viewModelScope.launch(dispatchers.main) { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - - scrollToWallet(index = action.selectedWalletIndex) - - withContext(dispatchers.io) { delay(timeMillis = 700) } - - stateHolder.update( - DeleteWalletTransformer( - selectedWalletIndex = action.selectedWalletIndex, - deletedWalletId = action.deletedWalletId, - ), - ) - } - } - - private fun unlockWallet(action: WalletsUpdateActionResolverV2.Action.UnlockWallet) { - viewModelScope.launch(dispatchers.main) { - withContext(dispatchers.io) { delay(timeMillis = 700) } - - stateHolder.update( - transformer = UnlockWalletTransformer( - unlockedWallets = action.unlockedWallets, - clickIntents = clickIntents, - ), - ) - - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - } - } - - private fun scrollToWallet(index: Int) { - stateHolder.update( - ScrollToWalletTransformer( - index = index, - currentStateProvider = Provider(action = stateHolder::value), - stateUpdater = { newState -> stateHolder.update { newState } }, - ), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index c8a6e6ca23..bdfd27558d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -4,140 +4,190 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.WalletLockedState -import com.tangem.feature.wallet.presentation.wallet.state.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.utils.Provider +import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import dagger.hilt.android.scopes.ViewModelScoped +import timber.log.Timber +import javax.inject.Inject /** * Resolver that determines which update action will be performed * - * @property currentStateProvider current state provider * @property getSelectedWalletSyncUseCase use case that returns selected wallet */ -internal class WalletsUpdateActionResolver( - private val currentStateProvider: Provider, +@ViewModelScoped +internal class WalletsUpdateActionResolver @Inject constructor( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, ) { - fun resolve(wallets: List): Action { - val selectedWallet = wallets.getSelectedWallet() + private var isInitialized: Boolean = false + private var canSaveWallets: Boolean = false - return when (val state = currentStateProvider()) { - is WalletState.Initial -> { - Action.Initialize( - selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId), - ) + fun resolve(wallets: List, currentState: WalletScreenState, canSaveWallets: Boolean): Action { + val selectedWallet = wallets.getSelectedWallet() ?: return Action.Unknown + + val action = when { + isFirstInitialization(currentState) -> { + createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) } - is WalletState.ContentState -> { - getActionToUpdateContent(state = state, wallets = wallets, selectedWallet = selectedWallet) + isReinitialization(canSaveWallets) -> { + this.canSaveWallets = canSaveWallets + Action.ReinitializeWallets(selectedWallet = selectedWallet) } + else -> getUpdateContentAction(currentState, wallets, selectedWallet) + } + + Timber.d("Resolved action: $action") + + return action + } + + private fun List.getSelectedWallet(): UserWallet? { + return when { + isEmpty() -> null + size == 1 -> first() + else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it }) } } - private fun List.getSelectedWallet(): UserWallet { - val hasUnlockedWallet = any { !it.isLocked } - return if (hasUnlockedWallet) { - val selectedWalletId = getSelectedWalletSyncUseCase().fold(ifLeft = ::error, ifRight = UserWallet::walletId) - - firstOrNull { it.walletId == selectedWalletId } - ?: error("Wallets don't contain a wallet with id: $selectedWalletId") - } else { - lastOrNull() ?: error("Wallets is empty") - } + private fun isFirstInitialization(state: WalletScreenState): Boolean { + return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } - private fun getActionToUpdateContent( - state: WalletState.ContentState, + private fun createInitializeWalletsAction( + wallets: List, + selectedWallet: UserWallet, + canSaveWallets: Boolean, + ): Action { + this.isInitialized = true + this.canSaveWallets = canSaveWallets + + return Action.InitializeWallets( + selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId), + selectedWallet = selectedWallet, + wallets = wallets, + ) + } + + private fun isReinitialization(canSaveWallets: Boolean): Boolean { + return isInitialized && this.canSaveWallets != canSaveWallets + } + + private fun getUpdateContentAction( + state: WalletScreenState, wallets: List, selectedWallet: UserWallet, ): Action { return when { isWalletsCountChanged(state, wallets) -> { - getActionToChangeWallets(state = state, wallets = wallets, selectedWallet = selectedWallet) + getChangeWalletsListAction(state, wallets, selectedWallet) } - isSelectedWalletChanged(state, selectedWallet) -> { - Action.Initialize(wallets.indexOfWallet(selectedWallet.walletId)) + isAnotherWalletSelected(state, selectedWallet) -> { + Action.ReinitializeWallet( + prevWalletId = state.getPrevSelectedWallet().id, + selectedWallet = selectedWallet, + ) } - else -> getActionToUpdateCurrentWallet(state = state, wallets = wallets, selectedWallet = selectedWallet) + else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet) } } - private fun isWalletsCountChanged(state: WalletState.ContentState, wallets: List): Boolean { - val prevWalletsSize = state.walletsListConfig.wallets.size + private fun isWalletsCountChanged(state: WalletScreenState, wallets: List): Boolean { + val prevWalletsSize = state.wallets.size val walletsSize = wallets.size return prevWalletsSize != walletsSize } - private fun getActionToChangeWallets( - state: WalletState.ContentState, + private fun getChangeWalletsListAction( + state: WalletScreenState, wallets: List, selectedWallet: UserWallet, ): Action { - val prevWalletsSize = state.walletsListConfig.wallets.size + val prevWalletsSize = state.wallets.size return when { prevWalletsSize > wallets.size -> { Action.DeleteWallet( - selectedWalletId = selectedWallet.walletId, + selectedWallet = selectedWallet, selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId), - deletedWalletId = state.walletsListConfig.wallets.getDeletedWalletId(wallets), + deletedWalletId = state.wallets.getDeletedWalletId(wallets), ) } prevWalletsSize < wallets.size -> { + val newUserWallet = state.wallets.getAddedWallet(wallets) Action.AddWallet( - selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId), + selectedWalletIndex = wallets.indexOfWallet(id = newUserWallet.walletId), + selectedWallet = newUserWallet, ) } - else -> Action.Unknown + else -> error("Wallets list is not changed") } } - private fun List.getDeletedWalletId(wallets: List): UserWalletId { + private fun List.getDeletedWalletId(wallets: List): UserWalletId { return this - .map(WalletCardState::id) + .map { it.walletCardState.id } .firstOrNull { !wallets.map(UserWallet::walletId).contains(it) } ?: error("Deleted wallet id is not found. Wallets contains all previous wallets ids") } - private fun isSelectedWalletChanged(state: WalletState.ContentState, selectedWallet: UserWallet): Boolean { + private fun List.getAddedWallet(wallets: List): UserWallet { + return wallets + .firstOrNull { wallet -> !this.map { it.walletCardState.id }.contains(wallet.walletId) } + ?: error("Added wallet id is not found. Wallets contains all previous wallets ids") + } + + private fun isAnotherWalletSelected(state: WalletScreenState, selectedWallet: UserWallet): Boolean { return state.getPrevSelectedWallet().id != selectedWallet.walletId } - private fun getActionToUpdateCurrentWallet( - state: WalletState.ContentState, + private fun getUpdateSelectedWalletAction( + state: WalletScreenState, wallets: List, selectedWallet: UserWallet, ): Action { - val selectedWalletName = selectedWallet.name - val previousWalletState = state.getPrevSelectedWallet() return when { - previousWalletState.title != selectedWalletName -> { - Action.UpdateWalletName(selectedWalletName) + isSelectedWalletNameChanged(state, selectedWallet) -> { + Action.UpdateWalletName(selectedWalletId = selectedWallet.walletId, name = selectedWallet.name) } - - state is WalletLockedState && !selectedWallet.isLocked -> { + isSelectedWalletUnlocked(state, selectedWallet) -> { Action.UnlockWallet( - selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId), selectedWallet = selectedWallet, unlockedWallets = wallets.filterNot(UserWallet::isLocked), ) } - - previousWalletState is WalletCardState.Content && - previousWalletState.cardCount != selectedWallet.getCardsCount() -> { - Action.UpdateWalletCardCount - } - + isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet) else -> Action.Unknown } } - private fun WalletState.ContentState.getPrevSelectedWallet(): WalletCardState { - val prevSelectedWalletIndex = walletsListConfig.selectedWalletIndex + private fun isSelectedWalletNameChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean { + return state.getPrevSelectedWallet().title != selectedWallet.name + } - return walletsListConfig.wallets.getOrNull(prevSelectedWalletIndex) + private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean { + return state.isSelectedWalletLocked() && !selectedWallet.isLocked + } + + private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean { + val prevSelectedWallet = state.getPrevSelectedWallet() + return prevSelectedWallet is WalletCardState.Content && + prevSelectedWallet.cardCount != selectedWallet.getCardsCount() + } + + private fun WalletScreenState.isSelectedWalletLocked(): Boolean { + val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found") + return selectedWalletState is WalletState.MultiCurrency.Locked || + selectedWalletState is WalletState.SingleCurrency.Locked + } + + private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState { + return wallets + .map(WalletState::walletCardState) + .getOrNull(selectedWalletIndex) ?: error("Previous selected wallet is not found") } @@ -153,26 +203,110 @@ internal class WalletsUpdateActionResolver( sealed class Action { - data class Initialize(val selectedWalletIndex: Int) : Action() - - data class UpdateWalletName(val name: String) : Action() - - data class UnlockWallet( + data class InitializeWallets( val selectedWalletIndex: Int, val selectedWallet: UserWallet, - val unlockedWallets: List, - ) : Action() + val wallets: List, + ) : Action() { + + override fun toString(): String { + return """ + InitializeWallets( + selectedWalletIndex = $selectedWalletIndex, + selectedWallet = ${selectedWallet.walletId}, + wallets = ${wallets.joinToString { it.walletId.toString() }} + ) + """.trimIndent() + } + } + + /** + * Reinitialize wallets. Example, if user turned on wallets saving + * + * @property selectedWallet selected wallet + */ + data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() { + + override fun toString(): String { + return "ReinitializeWallets(selectedWallet = ${selectedWallet.walletId})" + } + } + + /** + * Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off + * + * @property prevWalletId previous selected wallet id + * @property selectedWallet selected wallet + */ + data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() { + + override fun toString(): String { + return "ReinitializeWallet(prevWalletId = $prevWalletId, selectedWallet = ${selectedWallet.walletId})" + } + } + + data class UpdateWalletName(val selectedWalletId: UserWalletId, val name: String) : Action() { + + override fun toString(): String { + return "UpdateWalletName(selectedWalletId = $selectedWalletId, name = $name)" + } + } + + data class UnlockWallet(val selectedWallet: UserWallet, val unlockedWallets: List) : Action() { + + override fun toString(): String { + return """ + UnlockWallet( + selectedWallet = ${selectedWallet.walletId}, + unlockedWallets = ${unlockedWallets.joinToString { it.walletId.toString() }} + ) + """.trimIndent() + } + } data class DeleteWallet( - val selectedWalletId: UserWalletId, + val selectedWallet: UserWallet, val selectedWalletIndex: Int, val deletedWalletId: UserWalletId, - ) : Action() + ) : Action() { - data class AddWallet(val selectedWalletIndex: Int) : Action() + override fun toString(): String { + return """ + DeleteWallet( + selectedWallet = ${selectedWallet.walletId}, + selectedWalletIndex = $selectedWalletIndex, + deletedWalletId = $deletedWalletId + ) + """.trimIndent() + } + } - object UpdateWalletCardCount : Action() + data class AddWallet(val selectedWalletIndex: Int, val selectedWallet: UserWallet) : Action() { - object Unknown : Action() + override fun toString(): String { + return """ + AddWallet( + selectedWalletIndex = $selectedWalletIndex, + selectedWallet = ${selectedWallet.walletId} + ) + """.trimIndent() + } + } + + /** + * Update wallet card count. Example, if user backed up wallet + * + * @property selectedWallet selected wallet + */ + data class UpdateWalletCardCount(val selectedWallet: UserWallet) : Action() { + + override fun toString(): String { + return "UpdateWalletCardCount(selectedWallet = ${selectedWallet.walletId})" + } + } + + object Unknown : Action() { + override fun toString(): String = "Unknown" + } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolverV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolverV2.kt deleted file mode 100644 index 1c8a3f7b26..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolverV2.kt +++ /dev/null @@ -1,312 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.viewmodels - -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.model.NOT_INITIALIZED_WALLET_INDEX -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletScreenState -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import dagger.hilt.android.scopes.ViewModelScoped -import timber.log.Timber -import javax.inject.Inject - -/** - * Resolver that determines which update action will be performed - * - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - */ -@ViewModelScoped -internal class WalletsUpdateActionResolverV2 @Inject constructor( - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, -) { - - private var isInitialized: Boolean = false - private var canSaveWallets: Boolean = false - - fun resolve(wallets: List, currentState: WalletScreenState, canSaveWallets: Boolean): Action { - val selectedWallet = wallets.getSelectedWallet() ?: return Action.Unknown - - val action = when { - isFirstInitialization(currentState) -> { - createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) - } - isReinitialization(canSaveWallets) -> { - this.canSaveWallets = canSaveWallets - Action.ReinitializeWallets(selectedWallet = selectedWallet) - } - else -> getUpdateContentAction(currentState, wallets, selectedWallet) - } - - Timber.d("Resolved action: $action") - - return action - } - - private fun List.getSelectedWallet(): UserWallet? { - return when { - isEmpty() -> null - size == 1 -> first() - else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it }) - } - } - - private fun isFirstInitialization(state: WalletScreenState): Boolean { - return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX - } - - private fun createInitializeWalletsAction( - wallets: List, - selectedWallet: UserWallet, - canSaveWallets: Boolean, - ): Action { - this.isInitialized = true - this.canSaveWallets = canSaveWallets - - return Action.InitializeWallets( - selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId), - selectedWallet = selectedWallet, - wallets = wallets, - ) - } - - private fun isReinitialization(canSaveWallets: Boolean): Boolean { - return isInitialized && this.canSaveWallets != canSaveWallets - } - - private fun getUpdateContentAction( - state: WalletScreenState, - wallets: List, - selectedWallet: UserWallet, - ): Action { - return when { - isWalletsCountChanged(state, wallets) -> { - getChangeWalletsListAction(state, wallets, selectedWallet) - } - isAnotherWalletSelected(state, selectedWallet) -> { - Action.ReinitializeWallet( - prevWalletId = state.getPrevSelectedWallet().id, - selectedWallet = selectedWallet, - ) - } - else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet) - } - } - - private fun isWalletsCountChanged(state: WalletScreenState, wallets: List): Boolean { - val prevWalletsSize = state.wallets.size - val walletsSize = wallets.size - - return prevWalletsSize != walletsSize - } - - private fun getChangeWalletsListAction( - state: WalletScreenState, - wallets: List, - selectedWallet: UserWallet, - ): Action { - val prevWalletsSize = state.wallets.size - - return when { - prevWalletsSize > wallets.size -> { - Action.DeleteWallet( - selectedWallet = selectedWallet, - selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId), - deletedWalletId = state.wallets.getDeletedWalletId(wallets), - ) - } - prevWalletsSize < wallets.size -> { - val newUserWallet = state.wallets.getAddedWallet(wallets) - Action.AddWallet( - selectedWalletIndex = wallets.indexOfWallet(id = newUserWallet.walletId), - selectedWallet = newUserWallet, - ) - } - else -> error("Wallets list is not changed") - } - } - - private fun List.getDeletedWalletId(wallets: List): UserWalletId { - return this - .map { it.walletCardState.id } - .firstOrNull { !wallets.map(UserWallet::walletId).contains(it) } - ?: error("Deleted wallet id is not found. Wallets contains all previous wallets ids") - } - - private fun List.getAddedWallet(wallets: List): UserWallet { - return wallets - .firstOrNull { wallet -> !this.map { it.walletCardState.id }.contains(wallet.walletId) } - ?: error("Added wallet id is not found. Wallets contains all previous wallets ids") - } - - private fun isAnotherWalletSelected(state: WalletScreenState, selectedWallet: UserWallet): Boolean { - return state.getPrevSelectedWallet().id != selectedWallet.walletId - } - - private fun getUpdateSelectedWalletAction( - state: WalletScreenState, - wallets: List, - selectedWallet: UserWallet, - ): Action { - return when { - isSelectedWalletNameChanged(state, selectedWallet) -> { - Action.UpdateWalletName(selectedWalletId = selectedWallet.walletId, name = selectedWallet.name) - } - isSelectedWalletUnlocked(state, selectedWallet) -> { - Action.UnlockWallet( - selectedWallet = selectedWallet, - unlockedWallets = wallets.filterNot(UserWallet::isLocked), - ) - } - isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet) - else -> Action.Unknown - } - } - - private fun isSelectedWalletNameChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean { - return state.getPrevSelectedWallet().title != selectedWallet.name - } - - private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean { - return state.isSelectedWalletLocked() && !selectedWallet.isLocked - } - - private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean { - val prevSelectedWallet = state.getPrevSelectedWallet() - return prevSelectedWallet is WalletCardState.Content && - prevSelectedWallet.cardCount != selectedWallet.getCardsCount() - } - - private fun WalletScreenState.isSelectedWalletLocked(): Boolean { - val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found") - return selectedWalletState is WalletState.MultiCurrency.Locked || - selectedWalletState is WalletState.SingleCurrency.Locked - } - - private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState { - return wallets - .map(WalletState::walletCardState) - .getOrNull(selectedWalletIndex) - ?: error("Previous selected wallet is not found") - } - - private fun List.indexOfWallet(id: UserWalletId): Int { - val selectedIndex = indexOfFirst { it.walletId == id } - - return if (selectedIndex == -1) { - error("Wallets don't contain a wallet with id: $id") - } else { - selectedIndex - } - } - - sealed class Action { - - data class InitializeWallets( - val selectedWalletIndex: Int, - val selectedWallet: UserWallet, - val wallets: List, - ) : Action() { - - override fun toString(): String { - return """ - InitializeWallets( - selectedWalletIndex = $selectedWalletIndex, - selectedWallet = ${selectedWallet.walletId}, - wallets = ${wallets.joinToString { it.walletId.toString() }} - ) - """.trimIndent() - } - } - - /** - * Reinitialize wallets. Example, if user turned on wallets saving - * - * @property selectedWallet selected wallet - */ - data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return "ReinitializeWallets(selectedWallet = ${selectedWallet.walletId})" - } - } - - /** - * Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off - * - * @property prevWalletId previous selected wallet id - * @property selectedWallet selected wallet - */ - data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return "ReinitializeWallet(prevWalletId = $prevWalletId, selectedWallet = ${selectedWallet.walletId})" - } - } - - data class UpdateWalletName(val selectedWalletId: UserWalletId, val name: String) : Action() { - - override fun toString(): String { - return "UpdateWalletName(selectedWalletId = $selectedWalletId, name = $name)" - } - } - - data class UnlockWallet(val selectedWallet: UserWallet, val unlockedWallets: List) : Action() { - - override fun toString(): String { - return """ - UnlockWallet( - selectedWallet = ${selectedWallet.walletId}, - unlockedWallets = ${unlockedWallets.joinToString { it.walletId.toString() }} - ) - """.trimIndent() - } - } - - data class DeleteWallet( - val selectedWallet: UserWallet, - val selectedWalletIndex: Int, - val deletedWalletId: UserWalletId, - ) : Action() { - - override fun toString(): String { - return """ - DeleteWallet( - selectedWallet = ${selectedWallet.walletId}, - selectedWalletIndex = $selectedWalletIndex, - deletedWalletId = $deletedWalletId - ) - """.trimIndent() - } - } - - data class AddWallet(val selectedWalletIndex: Int, val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return """ - AddWallet( - selectedWalletIndex = $selectedWalletIndex, - selectedWallet = ${selectedWallet.walletId} - ) - """.trimIndent() - } - } - - /** - * Update wallet card count. Example, if user backed up wallet - * - * @property selectedWallet selected wallet - */ - data class UpdateWalletCardCount(val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return "UpdateWalletCardCount(selectedWallet = ${selectedWallet.walletId})" - } - } - - object Unknown : Action() { - override fun toString(): String = "Unknown" - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt index 5dad048ccd..b857c555b4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/VisaWalletIntents.kt @@ -7,10 +7,12 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModel import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.visa.GetVisaCurrencyUseCase +import com.tangem.domain.visa.GetVisaTxDetailsUseCase import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.BalancesAndLimitsBottomSheetConverter -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList @@ -23,6 +25,10 @@ internal interface VisaWalletIntents { fun onDepositClick() fun onBalancesAndLimitsClick() + + fun onVisaTransactionClick(id: String) + + fun onExploreClick(exploreUrl: String) } @ViewModelScoped @@ -31,6 +37,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor( private val eventSender: WalletEventSender, private val getCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase, private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase, + private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), VisaWalletIntents { @@ -93,4 +100,31 @@ internal class VisaWalletIntentsImplementor @Inject constructor( null } } + + override fun onVisaTransactionClick(id: String) { + viewModelScope.launch(dispatchers.main) { + val userWalletId = stateController.getSelectedWalletId() + val visaCurrency = getVisaCurrencyUseCase(userWalletId) + .getOrElse { + Timber.e(it, "Failed to get visa currency") + return@launch + } + val transactionDetails = getVisaTxDetailsUseCase(userWalletId, id) + .getOrElse { + Timber.e(it, "Failed to get transaction details") + return@launch + } + + val converter = VisaTxDetailsBottomSheetConverter( + visaCurrency, + clickIntents = this@VisaWalletIntentsImplementor, + ) + + stateController.showBottomSheet(content = converter.convert(transactionDetails)) + } + } + + override fun onExploreClick(exploreUrl: String) { + router.openUrl(exploreUrl) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index e8c30c9aac..fcbbdab810 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -7,12 +7,12 @@ import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.launch import timber.log.Timber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntentsV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt similarity index 92% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntentsV2.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 19c3f6130f..c27ff4424e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntentsV2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -12,10 +12,10 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetRefreshStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope @@ -24,7 +24,7 @@ import javax.inject.Inject @Suppress("LongParameterList") @ViewModelScoped -internal class WalletClickIntentsV2 @Inject constructor( +internal class WalletClickIntents @Inject constructor( private val walletCardClickIntentsImplementor: WalletCardClickIntentsImplementor, private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementer, private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor, @@ -70,7 +70,7 @@ internal class WalletClickIntentsV2 @Inject constructor( maybeUserWallet.onRight { walletScreenContentLoader.load( userWallet = it, - clickIntents = this@WalletClickIntentsV2, + clickIntents = this@WalletClickIntents, coroutineScope = viewModelScope, ) } @@ -138,7 +138,7 @@ internal class WalletClickIntentsV2 @Inject constructor( walletScreenContentLoader.load( userWallet = userWallet, - clickIntents = this@WalletClickIntentsV2, + clickIntents = this@WalletClickIntents, isRefresh = true, coroutineScope = viewModelScope, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index 6768550c5a..3ebb1b4830 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -5,7 +5,6 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.TokensAction -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase @@ -13,9 +12,9 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.domain.unwrap -import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.MultiWalletCurrencyActionsConverter +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.flow.collectLatest @@ -33,7 +32,7 @@ internal interface WalletContentClickIntents { fun onOrganizeTokensClick() - fun onTokenItemClick(currency: CryptoCurrency) + fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus) fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) @@ -69,9 +68,9 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId()) } - override fun onTokenItemClick(currency: CryptoCurrency) { + override fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus) { analyticsEventHandler.send(PortfolioEvent.TokenTapped) - router.openTokenDetails(stateHolder.getSelectedWalletId(), currency) + router.openTokenDetails(stateHolder.getSelectedWalletId(), currencyStatus) } override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 25c2b29527..243f372827 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -28,11 +28,11 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.domain.unwrap -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 0a32e8454a..5f6133df1b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -1,19 +1,10 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents -import com.tangem.blockchain.blockchains.cardano.CardanoUtils -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.card.EllipticCurve -import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.extensions.toMapKey import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.ui.extensions.resourceReference -import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.card.SetCardWasScannedUseCase -import com.tangem.domain.common.configs.CardConfig -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.NeverToSuggestRateAppUseCase @@ -24,20 +15,17 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase -import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError import com.tangem.feature.wallet.presentation.wallet.domain.unwrap -import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer -import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender -import com.tangem.features.tester.api.TesterFeatureToggles -import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent +import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.launch @@ -73,7 +61,6 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val updateWalletUseCase: UpdateWalletUseCase, private val unlockWalletsUseCase: UnlockWalletsUseCase, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler, @@ -83,7 +70,6 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val reduxStateHolder: ReduxStateHolder, - private val testerFeatureToggles: TesterFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { @@ -121,111 +107,15 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) viewModelScope.launch(dispatchers.main) { - if (testerFeatureToggles.isDerivePublicKeysRefactoringEnabled) { - derivePublicKeysUseCase( - userWalletId = userWallet.walletId, - currencies = missedAddressCurrencies, - ) - .onRight { fetchTokenListUseCase(userWalletId = userWallet.walletId) } - .onLeft { Timber.e("Failed to derive public keys: $it") } - } else { - // TODO: delete [REDACTED_JIRA] - deriveMissingCurrencies( - scanResponse = userWallet.scanResponse, - currencyList = missedAddressCurrencies, - ) { scannedCardResponse -> - updateWalletUseCase( - userWalletId = userWallet.walletId, - update = { it.copy(scanResponse = scannedCardResponse) }, - ) - .onRight { fetchTokenListUseCase(userWalletId = it.walletId) } - } - } + derivePublicKeysUseCase( + userWalletId = userWallet.walletId, + currencies = missedAddressCurrencies, + ) + .onRight { fetchTokenListUseCase(userWalletId = userWallet.walletId) } + .onLeft { Timber.e("Failed to derive public keys: $it") } } } - private fun deriveMissingCurrencies( - scanResponse: ScanResponse, - currencyList: List, - onSuccess: suspend (ScanResponse) -> Unit, - ) { - val config = CardConfig.createConfig(scanResponse.card) - val derivationDataList = currencyList.mapNotNull { - config.primaryCurve(blockchain = Blockchain.fromId(it.network.id.value))?.let { curve -> - getNewDerivations(curve, scanResponse, it) - } - } - - val derivations = buildMap> { - derivationDataList.forEach { - val current = this[it.derivations.first] - if (current != null) { - current.addAll(it.derivations.second) - current.distinct() - } else { - this[it.derivations.first] = it.derivations.second.toMutableList() - } - } - }.ifEmpty { return } - - viewModelScope.launch(dispatchers.io) { - derivePublicKeysUseCase(cardId = null, derivations = derivations) - .onRight { - val newDerivedKeys = it.entries - val oldDerivedKeys = scanResponse.derivedKeys - - val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() - - val updatedDerivedKeys = walletKeys.associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) - val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) - ExtendedPublicKeysMap(oldDerivations + newDerivations) - } - val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys) - - onSuccess(updatedScanResponse) - } - } - } - - private fun getNewDerivations( - curve: EllipticCurve, - scanResponse: ScanResponse, - currency: CryptoCurrency, - ): DerivationData? { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - - val blockchain = Blockchain.fromId(currency.network.id.value) - val supportedCurves = blockchain.getSupportedCurves() - val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) - .takeIf { supportedCurves.contains(curve) } - - val customPath = currency.network.derivationPath.value?.let { - DerivationPath(it) - }.takeIf { supportedCurves.contains(curve) } - - val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() - if (bothCandidates.isEmpty()) return null - - if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) { - currency.network.derivationPath.value?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } - } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys: ExtendedPublicKeysMap = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) - val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() - - val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } - if (toDerive.isEmpty()) return null - - return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive) - } - - class DerivationData(val derivations: Pair>) - override fun onOpenUnlockWalletsBottomSheetClick() { analyticsEventHandler.send(MainScreen.WalletUnlockTapped) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6d347dd3d4..b7b0ab03c0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -53,7 +53,6 @@ googlePlayServicesWallet = "19.1.0" hilt = "2.44" hilt-navigation = "1.0.0" jodatime = "2.12.1" -krateSharedPref = "2.0.0" kotlin-immutable-collections = "0.3.5" kotsonGsonExt = "2.5.0" lottie = "3.4.0" @@ -82,17 +81,18 @@ androidXCamera = "1.3.0" listenableFuture = "1.0" swipeRefreshLayout = "1.1.0" spr-client = "3.6.2" +web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.6-480" +tangemBlockchainSdk = "release-app_5.7-496" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.6-325" +tangemCardSdk = "release-app_5.7-329" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem # region Tools -detektComposeRules = "1.2.2" +detektComposeRules = "1.3.0" detekt = "1.22.0" # endregion Tools @@ -206,7 +206,6 @@ hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } hilt-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } hilt-navigation = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } jodatime = { module = "joda-time:joda-time", version.ref = "jodatime" } -krateSharedPref = { module = "hu.autsoft:krate", version.ref = "krateSharedPref" } kotsonGson = { module = "com.github.salomonbrys.kotson:kotson", version.ref = "kotsonGsonExt" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } material = { module = "com.google.android.material:material", version.ref = "googleMaterialComponent" } @@ -239,5 +238,5 @@ listenableFuture = { module = "com.google.guava:listenablefuture", version.ref = camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidXCamera" } camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidXCamera" } camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXCamera" } - +web3j-core = { module = "org.web3j:core", version.ref = "web3j" } # endregion Other diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index 60b1f6f10e..917e9c310d 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { /** SDK */ implementation(deps.tangem.blockchain) + + /** Core */ + implementation(projects.core.utils) } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt new file mode 100644 index 0000000000..1e7a2e6a6d --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -0,0 +1,29 @@ +package com.tangem.lib.crypto + +import com.tangem.blockchain.blockchains.xrp.XrpAddressService +import com.tangem.blockchain.common.Blockchain +import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter +import com.tangem.lib.crypto.models.XrpTaggedAddress + +/** + * !!!IMPORTANT!!! + * Methods for working with different blockchains + * All methods are depend on specific blockchain or check for specific blockchain + * + * Temporary solution for domain specific logic for Blockchain. + * Instead of creating repositories and unnecessary and overkill use cases + */ +object BlockchainUtils { + + private const val XRP_X_ADDRESS = 'X' + + /** Decodes XRP Blockchain address */ + fun decodeRippleXAddress(xAddress: String, networkId: String): XrpTaggedAddress? { + return if (networkId == Blockchain.XRP.id && xAddress.firstOrNull() == XRP_X_ADDRESS) { + val decodedAddress = XrpAddressService.decodeXAddress(xAddress) + return decodedAddress?.let(XrpTaggedAddressConverter()::convert) + } else { + null + } + } +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt deleted file mode 100644 index e3f12c83f6..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.lib.crypto - -import com.tangem.lib.crypto.models.Currency - -@Deprecated(message = "Use DerivePublicKeysUseCase instead") -interface DerivationManager { - - /** - * Makes derivation for [Currency] if it is missing and adds token to wallet - */ - suspend fun deriveAndAddTokens(currency: Currency): String -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt new file mode 100644 index 0000000000..9d42e0cbc4 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/converter/XrpTaggedAddressConverter.kt @@ -0,0 +1,15 @@ +package com.tangem.lib.crypto.converter + +import com.tangem.lib.crypto.models.XrpTaggedAddress +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.blockchains.xrp.XrpTaggedAddress as BlockchainXrpTaggedAddress + +internal class XrpTaggedAddressConverter : Converter { + + override fun convert(value: BlockchainXrpTaggedAddress): XrpTaggedAddress { + return XrpTaggedAddress( + address = value.address, + destinationTag = value.destinationTag, + ) + } +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt new file mode 100644 index 0000000000..d9d0946326 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/XrpTaggedAddress.kt @@ -0,0 +1,6 @@ +package com.tangem.lib.crypto.models + +data class XrpTaggedAddress( + val address: String, + val destinationTag: Long?, +) \ No newline at end of file diff --git a/libs/visa/.gitignore b/libs/visa/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/libs/visa/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libs/visa/build.gradle.kts b/libs/visa/build.gradle.kts new file mode 100644 index 0000000000..8ef254a4e4 --- /dev/null +++ b/libs/visa/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.libs.visa" +} + +dependencies { + + /** Project */ + implementation(projects.core.utils) + implementation(projects.core.datasource) + implementation(projects.data.common) + + /** Libs - Network */ + implementation(deps.moshi.kotlin) + implementation(deps.okHttp) + implementation(deps.okHttp.prettyLogging) + implementation(deps.retrofit) + implementation(deps.retrofit.moshi) + + /** Libs - Other */ + implementation(deps.web3j.core) + implementation(deps.kotlin.coroutines) + implementation(deps.arrow.fx) + implementation(deps.jodatime) +} \ No newline at end of file diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java b/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java new file mode 100644 index 0000000000..7b92736361 --- /dev/null +++ b/libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java @@ -0,0 +1,270 @@ +package com.tangem.lib.visa; + +import org.web3j.abi.EventEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.generated.Uint256; +import org.web3j.abi.datatypes.generated.Uint8; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Flowable; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.5.0. + */ +@SuppressWarnings("rawtypes") +class ERC20 extends Contract { + public static final String BINARY = "Bin file was not provided"; + + public static final String FUNC_ALLOWANCE = "allowance"; + + public static final String FUNC_APPROVE = "approve"; + + public static final String FUNC_BALANCEOF = "balanceOf"; + + public static final String FUNC_DECIMALS = "decimals"; + + public static final String FUNC_NAME = "name"; + + public static final String FUNC_SYMBOL = "symbol"; + + public static final String FUNC_TOTALSUPPLY = "totalSupply"; + + public static final String FUNC_TRANSFER = "transfer"; + + public static final String FUNC_TRANSFERFROM = "transferFrom"; + + public static final Event APPROVAL_EVENT = new Event("Approval", + Arrays.asList(new TypeReference

(true) { + }, new TypeReference
(true) { + }, new TypeReference() { + })); + + public static final Event TRANSFER_EVENT = new Event("Transfer", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference
(true) { + }, new TypeReference() { + })); + + @Deprecated + protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static List getApprovalEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(APPROVAL_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + ApprovalEventResponse typedResponse = new ApprovalEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.spender = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static ApprovalEventResponse getApprovalEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(APPROVAL_EVENT, log); + ApprovalEventResponse typedResponse = new ApprovalEventResponse(); + typedResponse.log = log; + typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.spender = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getTransferEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + TransferEventResponse typedResponse = new TransferEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + @Deprecated + public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TransferEventResponse getTransferEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log); + TransferEventResponse typedResponse = new TransferEventResponse(); + typedResponse.log = log; + typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return new ERC20(contractAddress, web3j, credentials, contractGasProvider); + } + + public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public Flowable transferEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransferEventFromLog(log)); + } + + public Flowable transferEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSFER_EVENT)); + return transferEventFlowable(filter); + } + + public Flowable approvalEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log)); + } + + public Flowable approvalEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(APPROVAL_EVENT)); + return approvalEventFlowable(filter); + } + + public RemoteFunctionCall allowance(String owner, String spender) { + final Function function = new Function(FUNC_ALLOWANCE, + Arrays.asList(new Address(160, owner), + new Address(160, spender)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall approve(String spender, BigInteger value) { + final Function function = new Function( + FUNC_APPROVE, + Arrays.asList(new Address(160, spender), + new Uint256(value)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall balanceOf(String account) { + final Function function = new Function(FUNC_BALANCEOF, + List.of(new Address(160, account)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall decimals() { + final Function function = new Function(FUNC_DECIMALS, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall name() { + final Function function = new Function(FUNC_NAME, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall symbol() { + final Function function = new Function(FUNC_SYMBOL, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall totalSupply() { + final Function function = new Function(FUNC_TOTALSUPPLY, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall transfer(String to, BigInteger value) { + final Function function = new Function( + FUNC_TRANSFER, + Arrays.asList(new Address(160, to), + new Uint256(value)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall transferFrom(String from, String to, BigInteger value) { + final Function function = new Function( + FUNC_TRANSFERFROM, + Arrays.asList(new Address(160, from), + new Address(160, to), + new Uint256(value)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public static class ApprovalEventResponse extends BaseEventResponse { + public String owner; + + public String spender; + + public BigInteger value; + } + + public static class TransferEventResponse extends BaseEventResponse { + public String from; + + public String to; + + public BigInteger value; + } +} diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java b/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java new file mode 100644 index 0000000000..f712f5cb24 --- /dev/null +++ b/libs/visa/src/main/java/com/tangem/lib/visa/TangemBridgeProcessor.java @@ -0,0 +1,1371 @@ +package com.tangem.lib.visa; + +import org.web3j.abi.EventEncoder; +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.Event; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.generated.Bytes32; +import org.web3j.abi.datatypes.generated.Bytes4; +import org.web3j.abi.datatypes.generated.Uint256; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Flowable; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.5.0. + */ +@SuppressWarnings("rawtypes") +class TangemBridgeProcessor extends Contract { + public static final String BINARY = "608060405262278d0060085534801562000017575f80fd5b5060405162002457380380620024578339810160408190526200003a91620001fa565b600580546001600160a01b038088166001600160a01b031992831617909255600680548784169083161790556002805486841690831617905560038054928516929091169190911790556009819055620000955f33620000a1565b5050505050506200025d565b5f80620000af8484620000dc565b90508015620000d3575f848152600160205260409020620000d1908462000187565b505b90505b92915050565b5f828152602081815260408083206001600160a01b038516845290915281205460ff166200017f575f838152602081815260408083206001600160a01b03861684529091529020805460ff19166001179055620001363390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4506001620000d6565b505f620000d6565b5f620000d3836001600160a01b0384165f8181526001830160205260408120546200017f57508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155620000d6565b80516001600160a01b0381168114620001f5575f80fd5b919050565b5f805f805f60a086880312156200020f575f80fd5b6200021a86620001de565b94506200022a60208701620001de565b93506200023a60408701620001de565b92506200024a60608701620001de565b9150608086015190509295509295909350565b6121ec806200026b5f395ff3fe608060405234801561000f575f80fd5b506004361061024a575f3560e01c806391792d5b11610140578063c904ea39116100bf578063d6d32b2a11610084578063d6d32b2a14610551578063da90f3a014610564578063e24fd1f114610577578063e452f20d1461058a578063e96ede8e1461059d578063fe2208aa146105b0575f80fd5b8063c904ea39146104fc578063ca15c8731461050f578063cb37f3b214610522578063d47ae89c14610535578063d547741f1461053e575f80fd5b8063a217fddf11610105578063a217fddf14610495578063b6878ac91461049c578063b77c6d35146104af578063be4d1851146104c2578063c6b273e4146104d5575f80fd5b806391792d5b1461041757806391d14854146104205780639353ce4c1461043357806396bc563d1461045a578063978975591461046e575f80fd5b80634413e098116101cc57806373d3467c1161019157806373d3467c146103b85780637f2fadb0146103cb5780638072a022146103de57806383af133d146103f15780639010d07c14610404575f80fd5b80634413e098146103445780635673795114610357578063601c60651461036b57806365ebf99a1461037e57806370e4306e14610391575f80fd5b80632f2ff15d116102125780632f2ff15d146102cc5780633013ce29146102df57806336568abe1461030a57806337de81061461031d5780633d409a8614610330575f80fd5b806301ffc9a71461024e5780630f1071be1461027657806311dce7711461028d578063248a9ca3146102a25780632cc32641146102c4575b5f80fd5b61026161025c366004611c84565b6105c3565b60405190151581526020015b60405180910390f35b61027f60085481565b60405190815260200161026d565b6102a061029b366004611cbf565b6105ed565b005b61027f6102b0366004611cda565b5f9081526020819052604090206001015490565b6102a061065a565b6102a06102da366004611cf1565b6106d2565b6003546102f2906001600160a01b031681565b6040516001600160a01b03909116815260200161026d565b6102a0610318366004611cf1565b6106fc565b6102a061032b366004611cda565b610734565b61027f5f8051602061216e83398151915281565b6102a0610352366004611d1f565b610780565b61027f5f805160206120f983398151915281565b6102a0610379366004611d49565b610847565b6102a061038c366004611cbf565b610a6a565b61027f7f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c81565b6102a06103c6366004611d88565b610acf565b6102a06103d9366004611d1f565b610bdc565b6102a06103ec366004611cda565b610cb1565b6102a06103ff366004611d1f565b610d3b565b6102f2610412366004611e06565b610df5565b61027f60075481565b61026161042e366004611cf1565b610e13565b61027f7f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b8325681565b61027f5f8051602061204983398151915281565b61027f7f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a8365681565b61027f5f81565b6102a06104aa366004611e26565b610e3b565b6002546102f2906001600160a01b031681565b6006546102f2906001600160a01b031681565b61027f7f56d36dba8e2d5c88fc1038bd95811d491e67c9a66c6848c9d4c9309b9587e06481565b6102a061050a366004611e5e565b610f3f565b61027f61051d366004611cda565b611030565b6005546102f2906001600160a01b031681565b61027f60095481565b6102a061054c366004611cf1565b611046565b6102a061055f366004611cda565b61106a565b6102a0610572366004611d1f565b6110f3565b6102a0610585366004611ea5565b611357565b6102f2610598366004611cbf565b611495565b6102a06105ab366004611d49565b6114f3565b6102a06105be366004611d49565b6115d6565b5f6001600160e01b03198216635a05180f60e01b14806105e757506105e7826116a1565b92915050565b5f80516020612049833981519152610604816116d5565b600680546001600160a01b0319166001600160a01b0384169081179091556040519081527f68c7b435e0ad7bfb2140fab0735300efbda206f62906049e0b35763cef67cc5e906020015b60405180910390a15050565b600a543390610668826116e2565b5f811160405180606001604052806029815260200161218e60299139906106ab5760405162461bcd60e51b81526004016106a29190611ef3565b60405180910390fd5b505f600a556006546003546106ce916001600160a01b0391821691168484611785565b5050565b5f828152602081905260409020600101546106ec816116d5565b6106f683836117df565b50505050565b6001600160a01b03811633146107255760405163334bd91960e11b815260040160405180910390fd5b61072f8282611812565b505050565b5f8051602061204983398151915261074b816116d5565b60078290556040518281527fb5aa183eb20407e22587bcd13d5c82a85835a4bd60e2a13d7c7efee3c2e9ed489060200161064e565b7f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a836566107aa816116d5565b60405163b9603bdf60e01b8152600481018390526001600160a01b0384169063b9603bdf906024015f604051808303815f87803b1580156107e9575f80fd5b505af11580156107fb573d5f803e3d5ffd5b50505050826001600160a01b03167fc8818db5b3e4e986e2f21e002090b1513e17c05a7a326c3737af624bf6ef78c48360405161083a91815260200190565b60405180910390a2505050565b5f805160206120f983398151915261085e816116d5565b60408051808201909152601b81527f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f00000000006020820152826108b05760405162461bcd60e51b81526004016106a29190611ef3565b505f6108bb85611495565b6003546040516370a0823160e01b81526001600160a01b0380841660048301529293505f92909116906370a0823190602401602060405180830381865afa158015610908573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061092c9190611f25565b9050838110156040518060400160405280601f81526020017f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e647300815250906109865760405162461bcd60e51b81526004016106a29190611ef3565b50600a84905560405163f89db45760e01b8152600481018690526001600160a01b0383169063f89db457906024015f604051808303815f87803b1580156109cb575f80fd5b505af11580156109dd573d5f803e3d5ffd5b50505050600a545f146040518060600160405280602e8152602001612119602e913990610a1d5760405162461bcd60e51b81526004016106a29190611ef3565b5084866001600160a01b03167fa5411d116afcb8415f0019792cccf595f94852ba82886dacbf756d9c3ce85f3f86604051610a5a91815260200190565b60405180910390a3505050505050565b5f80516020612049833981519152610a81816116d5565b600580546001600160a01b0319166001600160a01b0384169081179091556040519081527fccbdeb71dc680b2f0fd85f93e7ef0f70eda32a12b918c9715be07bb646360e599060200161064e565b5f8051602061216e833981519152610ae6816116d5565b5f610af088611495565b90505f60075487610b019190611f50565b6040516334284dfb60e01b8152600481018a9052602481018290526001600160801b03198816604482015261ffff8716606482015285151560848201529091506001600160a01b038316906334284dfb9060a4015f604051808303815f87803b158015610b6c575f80fd5b505af1158015610b7e573d5f803e3d5ffd5b5050505087896001600160a01b03167f92c1dbadbb7b791052341ce7c185ca37861e432438355dc3e1642b7c1287609a89600754604051610bc9929190918252602082015260400190565b60405180910390a3505050505050505050565b7f79af5a85184b45263c1b3721b9c8eb1becd98e55ade5a61da0fed18b90b83256610c06816116d5565b5f610c1084611495565b604051636df6c7d560e11b8152600481018590529091506001600160a01b0382169063dbed8faa906024015f604051808303815f87803b158015610c52575f80fd5b505af1158015610c64573d5f803e3d5ffd5b50505050836001600160a01b03167f466723bb873015d5baf515fce6b0c8df2cb154adfd8badb6bc77a620a6ef676184604051610ca391815260200190565b60405180910390a250505050565b5f80516020612049833981519152610cc8816116d5565b6283d60082106040518060600160405280602a815260200161201f602a913990610d055760405162461bcd60e51b81526004016106a29190611ef3565b5060088290556040518281527fdc10143650bb79cd7a92cdf792545dcc2c3b0a719bebb3d83a8a21bb8fbfc3d69060200161064e565b7f581be84b4822abe392a3bcac0d4d656e4cd57faf2e321a32108215cdd4a83656610d65816116d5565b6040516306b2a38360e41b8152600481018390526001600160a01b03841690636b2a3830906024015f604051808303815f87803b158015610da4575f80fd5b505af1158015610db6573d5f803e3d5ffd5b50505050826001600160a01b03167fcc1685e553848099cad0e11271e780371c44b5ed92a54552fbedf9f1b6b9f2478360405161083a91815260200190565b5f828152600160205260408120610e0c908361183d565b9392505050565b5f918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b7f6ac2c8b5322a929d300af17766189693ff6db6939ffe690430ad4720baf47d1c610e65816116d5565b5f610e6f86611495565b90505f60075484610e809190611f50565b6040516304e7595f60e21b81526004810188905260248101879052604481018290529091506001600160a01b0383169063139d657c906064015f604051808303815f87803b158015610ed0575f80fd5b505af1158015610ee2573d5f803e3d5ffd5b505050508486886001600160a01b03167fcb06dfb173e98cd60a8931644d288167e193c33d81c1019b9f9aab36b13fbc0387600754604051610f2e929190918252602082015260400190565b60405180910390a450505050505050565b5f8051602061216e833981519152610f56816116d5565b5f610f6086611495565b90505f60075485610f719190611f50565b6040516320199e7960e21b8152600481018890526024810182905285151560448201529091506001600160a01b0383169063806679e4906064015f604051808303815f87803b158015610fc2575f80fd5b505af1158015610fd4573d5f803e3d5ffd5b5050505085876001600160a01b03167f92c1dbadbb7b791052341ce7c185ca37861e432438355dc3e1642b7c1287609a8760075460405161101f929190918252602082015260400190565b60405180910390a350505050505050565b5f8181526001602052604081206105e790611848565b5f82815260208190526040902060010154611060816116d5565b6106f68383611812565b5f80516020612049833981519152611081816116d5565b610e10821060405180606001604052806027815260200161214760279139906110bd5760405162461bcd60e51b81526004016106a29190611ef3565b5060098290556040518281527f7f63f876249f25f7856e620802edd09af38a83c9c6040fcdb3717915403083119060200161064e565b5f805160206120f983398151915261110a816116d5565b5f61111484611495565b604051631902cad960e31b8152600481018590529091505f906001600160a01b0383169063c81656c890602401602060405180830381865afa15801561115c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111809190611f25565b6003546040516370a0823160e01b81526001600160a01b0385811660048301529293505f92909116906370a0823190602401602060405180830381865afa1580156111cd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111f19190611f25565b90505f82116040518060600160405280602781526020016120d2602791399061122d5760405162461bcd60e51b81526004016106a29190611ef3565b5060408051808201909152601f81527f353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e6473006020820152828210156112835760405162461bcd60e51b81526004016106a29190611ef3565b50600a82905560405163e3dbffd560e01b8152600481018690526001600160a01b0384169063e3dbffd5906024015f604051808303815f87803b1580156112c8575f80fd5b505af11580156112da573d5f803e3d5ffd5b50505050600a545f146040518060600160405280602e8152602001612119602e91399061131a5760405162461bcd60e51b81526004016106a29190611ef3565b5084866001600160a01b03167fdfab01fc691e8b69fca482c65142d1bcfeafeb9eb6e9fd5220867d7196d9476e84604051610a5a91815260200190565b7f56d36dba8e2d5c88fc1038bd95811d491e67c9a66c6848c9d4c9309b9587e064611381816116d5565b61138a826116e2565b306001600160a01b0316826001600160a01b031663ce1b1d436040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113d0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113f49190611f63565b6001600160a01b0316146040518060600160405280603281526020016120a060329139906114355760405162461bcd60e51b81526004016106a29190611ef3565b506001600160a01b038381165f8181526004602090815260409182902080546001600160a01b0319169487169485179055905192835290917f0c1464e8c817204497a2076f3b466fe64093e83033c83303b0cb44001cd9b6b5910161083a565b6001600160a01b038082165f90815260046020908152604080832054815160608101909252602f8083529394169283151592611ff090830139906114ec5760405162461bcd60e51b81526004016106a29190611ef3565b5092915050565b5f8051602061216e83398151915261150a816116d5565b5f61151485611495565b90505f831561152557600754611527565b5f5b90505f6115348286611f50565b60405163e4d7ddb960e01b815260048101889052602481018290529091506001600160a01b0384169063e4d7ddb9906044015f604051808303815f87803b15801561157d575f80fd5b505af115801561158f573d5f803e3d5ffd5b505060408051888152602081018690528993506001600160a01b038b1692507f8ba3ad1bd4ff5c3d75861d56df66b7942244d700f4d4cb67df693bac78e805a2910161101f565b5f805160206120f98339815191526115ed816116d5565b5f6115f785611495565b60405163456575e560e01b815260048101869052602481018590529091506001600160a01b0382169063456575e5906044015f604051808303815f87803b158015611640575f80fd5b505af1158015611652573d5f803e3d5ffd5b5050505083856001600160a01b03167f6f60b80b8dcecc3741c03485554a9a35ced96ebba66405fd324caf12a16261da8560405161169291815260200190565b60405180910390a35050505050565b5f6001600160e01b03198216637965db0b60e01b14806105e757506301ffc9a760e01b6001600160e01b03198316146105e7565b6116df8133611851565b50565b6002546040516385bb392360e01b81526001600160a01b038381166004830152909116906385bb392390602401602060405180830381865afa15801561172a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174e9190611f7e565b60405180606001604052806037815260200161206960379139906106ce5760405162461bcd60e51b81526004016106a29190611ef3565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b1790526106f690859061188a565b5f806117eb84846118eb565b90508015610e0c575f84815260016020526040902061180a908461197a565b509392505050565b5f8061181e848461198e565b90508015610e0c575f84815260016020526040902061180a90846119f7565b5f610e0c8383611a0b565b5f6105e7825490565b61185b8282610e13565b6106ce5760405163e2517d3f60e01b81526001600160a01b0382166004820152602481018390526044016106a2565b5f61189e6001600160a01b03841683611a31565b905080515f141580156118c25750808060200190518101906118c09190611f7e565b155b1561072f57604051635274afe760e01b81526001600160a01b03841660048201526024016106a2565b5f6118f68383610e13565b611973575f838152602081815260408083206001600160a01b03861684529091529020805460ff1916600117905561192b3390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45060016105e7565b505f6105e7565b5f610e0c836001600160a01b038416611a3e565b5f6119998383610e13565b15611973575f838152602081815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45060016105e7565b5f610e0c836001600160a01b038416611a83565b5f825f018281548110611a2057611a20611f99565b905f5260205f200154905092915050565b6060610e0c83835f611b66565b5f81815260018301602052604081205461197357508154600181810184555f8481526020808220909301849055845484825282860190935260409020919091556105e7565b5f8181526001830160205260408120548015611b5d575f611aa5600183611fad565b85549091505f90611ab890600190611fad565b9050808214611b17575f865f018281548110611ad657611ad6611f99565b905f5260205f200154905080875f018481548110611af657611af6611f99565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080611b2857611b28611fc0565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f9055600193505050506105e7565b5f9150506105e7565b606081471015611b8b5760405163cd78605960e01b81523060048201526024016106a2565b5f80856001600160a01b03168486604051611ba69190611fd4565b5f6040518083038185875af1925050503d805f8114611be0576040519150601f19603f3d011682016040523d82523d5f602084013e611be5565b606091505b5091509150611bf5868383611bff565b9695505050505050565b606082611c1457611c0f82611c5b565b610e0c565b8151158015611c2b57506001600160a01b0384163b155b15611c5457604051639996b31560e01b81526001600160a01b03851660048201526024016106a2565b5080610e0c565b805115611c6b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f60208284031215611c94575f80fd5b81356001600160e01b031981168114610e0c575f80fd5b6001600160a01b03811681146116df575f80fd5b5f60208284031215611ccf575f80fd5b8135610e0c81611cab565b5f60208284031215611cea575f80fd5b5035919050565b5f8060408385031215611d02575f80fd5b823591506020830135611d1481611cab565b809150509250929050565b5f8060408385031215611d30575f80fd5b8235611d3b81611cab565b946020939093013593505050565b5f805f60608486031215611d5b575f80fd5b8335611d6681611cab565b95602085013595506040909401359392505050565b80151581146116df575f80fd5b5f805f805f8060c08789031215611d9d575f80fd5b8635611da881611cab565b9550602087013594506040870135935060608701356001600160801b031981168114611dd2575f80fd5b9250608087013561ffff81168114611de8575f80fd5b915060a0870135611df881611d7b565b809150509295509295509295565b5f8060408385031215611e17575f80fd5b50508035926020909101359150565b5f805f8060808587031215611e39575f80fd5b8435611e4481611cab565b966020860135965060408601359560600135945092505050565b5f805f8060808587031215611e71575f80fd5b8435611e7c81611cab565b935060208501359250604085013591506060850135611e9a81611d7b565b939692955090935050565b5f8060408385031215611eb6575f80fd5b8235611ec181611cab565b91506020830135611d1481611cab565b5f5b83811015611eeb578181015183820152602001611ed3565b50505f910152565b602081525f8251806020840152611f11816040850160208701611ed1565b601f01601f19169190910160400192915050565b5f60208284031215611f35575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156105e7576105e7611f3c565b5f60208284031215611f73575f80fd5b8151610e0c81611cab565b5f60208284031215611f8e575f80fd5b8151610e0c81611d7b565b634e487b7160e01b5f52603260045260245ffd5b818103818111156105e7576105e7611f3c565b634e487b7160e01b5f52603160045260245ffd5b5f8251611fe5818460208701611ed1565b919091019291505056fe353230327c50726f636573736f723a206e6f207061796d656e74206163636f756e7420666f72207468652063617264353231307c50726f636573736f723a20736574746c656d656e7420706572696f6420746f6f206c6f6e67fa658e47c75a6eb0e27156b2f72e1b295c98b53e8a1d238d7ca6ea6345b34880353230307c50726f636573736f723a207061796d656e74206163636f756e74206e6f74206465706c6f79656420627920666163746f7279353230317c50726f636573736f723a207061796d656e74206163636f756e742070726f636573736f72206d69736d61746368353235307c50726f636573736f723a20726566756e64207265636f7264206e6f7420666f756e64d77cc12a543481a2b3ef8fd055979569715a10db9879cd9e664395eca3a54dff353235327c50726f636573736f723a20726566756e6420746f2070726f63657373207761736e2774207265736574353231317c50726f636573736f723a2073656375726974792064656c617920746f6f206c6f6e67a567244cad934c87b7f7e15b15dd0e75f95a76afd1916f8eab4a391410d6f278353235317c50726f636573736f723a20726566756e6420746f2070726f63657373206973207a65726fa2646970667358221220abcbacdb9048a2744f99cdd5031db80dd0390bf503ccade142542ce178f0668364736f6c63430008160033"; + + public static final String FUNC_AUTHORIZATION_PROCESSOR_ROLE = "AUTHORIZATION_PROCESSOR_ROLE"; + + public static final String FUNC_BALANCE_VERIFIER_ROLE = "BALANCE_VERIFIER_ROLE"; + + public static final String FUNC_DEBT_PROCESSOR_ROLE = "DEBT_PROCESSOR_ROLE"; + + public static final String FUNC_DEFAULT_ADMIN_ROLE = "DEFAULT_ADMIN_ROLE"; + + public static final String FUNC_PAYMENT_ACCOUNT_SETTER = "PAYMENT_ACCOUNT_SETTER"; + + public static final String FUNC_PROPERTY_SETTER_ROLE = "PROPERTY_SETTER_ROLE"; + + public static final String FUNC_REFUND_PROCESSOR_ROLE = "REFUND_PROCESSOR_ROLE"; + + public static final String FUNC_SETTLEMENT_PROCESSOR_ROLE = "SETTLEMENT_PROCESSOR_ROLE"; + + public static final String FUNC_FIXEDFEE = "fixedFee"; + + public static final String FUNC_GETPAYMENTACCOUNT = "getPaymentAccount"; + + public static final String FUNC_GETROLEADMIN = "getRoleAdmin"; + + public static final String FUNC_GETROLEMEMBER = "getRoleMember"; + + public static final String FUNC_GETROLEMEMBERCOUNT = "getRoleMemberCount"; + + public static final String FUNC_GRANTROLE = "grantRole"; + + public static final String FUNC_HASROLE = "hasRole"; + + public static final String FUNC_INCREASEVERIFIEDBALANCEFOR = "increaseVerifiedBalanceFor"; + + public static final String FUNC_PAYMENTACCOUNTFACTORY = "paymentAccountFactory"; + + public static final String FUNC_PAYMENTRECEIVER = "paymentReceiver"; + + public static final String FUNC_PAYMENTTOKEN = "paymentToken"; + + public static final String FUNC_PROCESSAUTHORIZATION = "processAuthorization"; + + public static final String FUNC_PROCESSAUTHORIZATIONCHANGE = "processAuthorizationChange"; + + public static final String FUNC_PROCESSAUTHORIZATIONNOOTP = "processAuthorizationNoOtp"; + + public static final String FUNC_PROCESSPENDINGREFUND = "processPendingRefund"; + + public static final String FUNC_PROCESSREFUND = "processRefund"; + + public static final String FUNC_PROCESSREFUNDCALLBACK = "processRefundCallback"; + + public static final String FUNC_PROCESSSETTLEMENT = "processSettlement"; + + public static final String FUNC_REFUNDACCOUNT = "refundAccount"; + + public static final String FUNC_RENOUNCEROLE = "renounceRole"; + + public static final String FUNC_REVOKEROLE = "revokeRole"; + + public static final String FUNC_SAVEPENDINGREFUND = "savePendingRefund"; + + public static final String FUNC_SECURITYDELAY = "securityDelay"; + + public static final String FUNC_SETFIXEDFEE = "setFixedFee"; + + public static final String FUNC_SETPAYMENTACCOUNT = "setPaymentAccount"; + + public static final String FUNC_SETPAYMENTRECEIVER = "setPaymentReceiver"; + + public static final String FUNC_SETREFUNDACCOUNT = "setRefundAccount"; + + public static final String FUNC_SETSECURITYDELAY = "setSecurityDelay"; + + public static final String FUNC_SETSETTLEMENTPERIOD = "setSettlementPeriod"; + + public static final String FUNC_SETVERIFIEDBALANCEFOR = "setVerifiedBalanceFor"; + + public static final String FUNC_SETTLEMENTPERIOD = "settlementPeriod"; + + public static final String FUNC_SUPPORTSINTERFACE = "supportsInterface"; + + public static final String FUNC_WRITEOFFDEBT = "writeOffDebt"; + + public static final Event AUTHORIZATIONCHANGEPROCESSED_EVENT = new Event("AuthorizationChangeProcessed", + Arrays.asList(new TypeReference

(true) { + }, new TypeReference(true) { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event AUTHORIZATIONPROCESSED_EVENT = new Event("AuthorizationProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference(true) { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event DEBTWRITEOFFPROCESSED_EVENT = new Event("DebtWriteOffProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference() { + })); + + public static final Event FIXEDFEESET_EVENT = new Event("FixedFeeSet", + List.of(new TypeReference() { + })); + + public static final Event PAYMENTACCOUNTSET_EVENT = new Event("PaymentAccountSet", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference
() { + })); + + public static final Event PAYMENTRECEIVERSET_EVENT = new Event("PaymentReceiverSet", + List.of(new TypeReference
() { + })); + + public static final Event PENDINGREFUNDPROCESSED_EVENT = new Event("PendingRefundProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event REFUNDACCOUNTSET_EVENT = new Event("RefundAccountSet", + List.of(new TypeReference
() { + })); + + public static final Event REFUNDPROCESSED_EVENT = new Event("RefundProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event ROLEADMINCHANGED_EVENT = new Event("RoleAdminChanged", + Arrays.asList(new TypeReference(true) { + }, new TypeReference(true) { + }, new TypeReference(true) { + })); + + public static final Event ROLEGRANTED_EVENT = new Event("RoleGranted", + Arrays.asList(new TypeReference(true) { + }, new TypeReference
(true) { + }, new TypeReference
(true) { + })); + + public static final Event ROLEREVOKED_EVENT = new Event("RoleRevoked", + Arrays.asList(new TypeReference(true) { + }, new TypeReference
(true) { + }, new TypeReference
(true) { + })); + + public static final Event SAVEREFUNDPROCESSED_EVENT = new Event("SaveRefundProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event SECURITYDELAYSET_EVENT = new Event("SecurityDelaySet", + List.of(new TypeReference() { + })); + + public static final Event SETTLEMENTPERIODSET_EVENT = new Event("SettlementPeriodSet", + List.of(new TypeReference() { + })); + + public static final Event SETTLEMENTPROCESSED_EVENT = new Event("SettlementProcessed", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference(true) { + }, new TypeReference(true) { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event VERIFIEDBALANCEINCREASEDFOR_EVENT = new Event("VerifiedBalanceIncreasedFor", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference() { + })); + + public static final Event VERIFIEDBALANCESETFOR_EVENT = new Event("VerifiedBalanceSetFor", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference() { + })); + + @Deprecated + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected TangemBridgeProcessor(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static List getAuthorizationChangeProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AuthorizationChangeProcessedEventResponse getAuthorizationChangeProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONCHANGEPROCESSED_EVENT, log); + AuthorizationChangeProcessedEventResponse typedResponse = new AuthorizationChangeProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static List getAuthorizationProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + AuthorizationProcessedEventResponse typedResponse = new AuthorizationProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getDebtWriteOffProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + DebtWriteOffProcessedEventResponse typedResponse = new DebtWriteOffProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getFixedFeeSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(FIXEDFEESET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + FixedFeeSetEventResponse typedResponse = new FixedFeeSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.fixedFee = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AuthorizationProcessedEventResponse getAuthorizationProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(AUTHORIZATIONPROCESSED_EVENT, log); + AuthorizationProcessedEventResponse typedResponse = new AuthorizationProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static List getPaymentAccountSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PAYMENTACCOUNTSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + PaymentAccountSetEventResponse typedResponse = new PaymentAccountSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getPaymentReceiverSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + PaymentReceiverSetEventResponse typedResponse = new PaymentReceiverSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentReceiver = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getPendingRefundProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PENDINGREFUNDPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + PendingRefundProcessedEventResponse typedResponse = new PendingRefundProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static DebtWriteOffProcessedEventResponse getDebtWriteOffProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITEOFFPROCESSED_EVENT, log); + DebtWriteOffProcessedEventResponse typedResponse = new DebtWriteOffProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static PendingRefundProcessedEventResponse getPendingRefundProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PENDINGREFUNDPROCESSED_EVENT, log); + PendingRefundProcessedEventResponse typedResponse = new PendingRefundProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getRefundAccountSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RefundAccountSetEventResponse typedResponse = new RefundAccountSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.refundAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getRefundProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RefundProcessedEventResponse typedResponse = new RefundProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static FixedFeeSetEventResponse getFixedFeeSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(FIXEDFEESET_EVENT, log); + FixedFeeSetEventResponse typedResponse = new FixedFeeSetEventResponse(); + typedResponse.log = log; + typedResponse.fixedFee = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getRoleAdminChangedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RoleAdminChangedEventResponse typedResponse = new RoleAdminChangedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.previousAdminRole = (byte[]) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newAdminRole = (byte[]) eventValues.getIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getRoleGrantedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RoleGrantedEventResponse typedResponse = new RoleGrantedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getRoleRevokedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RoleRevokedEventResponse typedResponse = new RoleRevokedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static PaymentAccountSetEventResponse getPaymentAccountSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTACCOUNTSET_EVENT, log); + PaymentAccountSetEventResponse typedResponse = new PaymentAccountSetEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.paymentAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getSaveRefundProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SAVEREFUNDPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + SaveRefundProcessedEventResponse typedResponse = new SaveRefundProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getSecurityDelaySetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + SecurityDelaySetEventResponse typedResponse = new SecurityDelaySetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.securityDelay = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getSettlementPeriodSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + SettlementPeriodSetEventResponse typedResponse = new SettlementPeriodSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.settlementPeriod = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static PaymentReceiverSetEventResponse getPaymentReceiverSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PAYMENTRECEIVERSET_EVENT, log); + PaymentReceiverSetEventResponse typedResponse = new PaymentReceiverSetEventResponse(); + typedResponse.log = log; + typedResponse.paymentReceiver = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getSettlementProcessedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + SettlementProcessedEventResponse typedResponse = new SettlementProcessedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.settlementId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getVerifiedBalanceIncreasedForEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + VerifiedBalanceIncreasedForEventResponse typedResponse = new VerifiedBalanceIncreasedForEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getVerifiedBalanceSetForEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + VerifiedBalanceSetForEventResponse typedResponse = new VerifiedBalanceSetForEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + @Deprecated + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemBridgeProcessor(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return new TangemBridgeProcessor(contractAddress, web3j, credentials, contractGasProvider); + } + + public static TangemBridgeProcessor load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new TangemBridgeProcessor(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RefundAccountSetEventResponse getRefundAccountSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDACCOUNTSET_EVENT, log); + RefundAccountSetEventResponse typedResponse = new RefundAccountSetEventResponse(); + typedResponse.log = log; + typedResponse.refundAccount = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), + new Address(160, refundAccount_), + new Address(160, paymentAccountFactory_), + new Address(160, paymentToken_), + new Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), + new Address(160, refundAccount_), + new Address(160, paymentAccountFactory_), + new Address(160, paymentToken_), + new Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), + new Address(160, refundAccount_), + new Address(160, paymentAccountFactory_), + new Address(160, paymentToken_), + new Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + public static RefundProcessedEventResponse getRefundProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPROCESSED_EVENT, log); + RefundProcessedEventResponse typedResponse = new RefundProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String paymentReceiver_, String refundAccount_, String paymentAccountFactory_, String paymentToken_, BigInteger securityDelay_) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Address(160, paymentReceiver_), + new Address(160, refundAccount_), + new Address(160, paymentAccountFactory_), + new Address(160, paymentToken_), + new Uint256(securityDelay_))); + return deployRemoteCall(TangemBridgeProcessor.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + public static RoleAdminChangedEventResponse getRoleAdminChangedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEADMINCHANGED_EVENT, log); + RoleAdminChangedEventResponse typedResponse = new RoleAdminChangedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.previousAdminRole = (byte[]) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.newAdminRole = (byte[]) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; + } + + public static RoleGrantedEventResponse getRoleGrantedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEGRANTED_EVENT, log); + RoleGrantedEventResponse typedResponse = new RoleGrantedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; + } + + public static RoleRevokedEventResponse getRoleRevokedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ROLEREVOKED_EVENT, log); + RoleRevokedEventResponse typedResponse = new RoleRevokedEventResponse(); + typedResponse.log = log; + typedResponse.role = (byte[]) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.account = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.sender = (String) eventValues.getIndexedValues().get(2).getValue(); + return typedResponse; + } + + public static SaveRefundProcessedEventResponse getSaveRefundProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SAVEREFUNDPROCESSED_EVENT, log); + SaveRefundProcessedEventResponse typedResponse = new SaveRefundProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static SecurityDelaySetEventResponse getSecurityDelaySetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SECURITYDELAYSET_EVENT, log); + SecurityDelaySetEventResponse typedResponse = new SecurityDelaySetEventResponse(); + typedResponse.log = log; + typedResponse.securityDelay = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static SettlementPeriodSetEventResponse getSettlementPeriodSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPERIODSET_EVENT, log); + SettlementPeriodSetEventResponse typedResponse = new SettlementPeriodSetEventResponse(); + typedResponse.log = log; + typedResponse.settlementPeriod = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static SettlementProcessedEventResponse getSettlementProcessedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(SETTLEMENTPROCESSED_EVENT, log); + SettlementProcessedEventResponse typedResponse = new SettlementProcessedEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.settlementId = (BigInteger) eventValues.getIndexedValues().get(2).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.fee = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static VerifiedBalanceIncreasedForEventResponse getVerifiedBalanceIncreasedForEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASEDFOR_EVENT, log); + VerifiedBalanceIncreasedForEventResponse typedResponse = new VerifiedBalanceIncreasedForEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static VerifiedBalanceSetForEventResponse getVerifiedBalanceSetForEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESETFOR_EVENT, log); + VerifiedBalanceSetForEventResponse typedResponse = new VerifiedBalanceSetForEventResponse(); + typedResponse.log = log; + typedResponse.paymentAccount = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public Flowable authorizationChangeProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthorizationChangeProcessedEventFromLog(log)); + } + + public Flowable authorizationChangeProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONCHANGEPROCESSED_EVENT)); + return authorizationChangeProcessedEventFlowable(filter); + } + + public Flowable authorizationProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAuthorizationProcessedEventFromLog(log)); + } + + public Flowable authorizationProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(AUTHORIZATIONPROCESSED_EVENT)); + return authorizationProcessedEventFlowable(filter); + } + + public Flowable debtWriteOffProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtWriteOffProcessedEventFromLog(log)); + } + + public Flowable debtWriteOffProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTWRITEOFFPROCESSED_EVENT)); + return debtWriteOffProcessedEventFlowable(filter); + } + + public Flowable fixedFeeSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getFixedFeeSetEventFromLog(log)); + } + + public Flowable fixedFeeSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(FIXEDFEESET_EVENT)); + return fixedFeeSetEventFlowable(filter); + } + + public Flowable paymentAccountSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPaymentAccountSetEventFromLog(log)); + } + + public Flowable paymentAccountSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PAYMENTACCOUNTSET_EVENT)); + return paymentAccountSetEventFlowable(filter); + } + + public Flowable paymentReceiverSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPaymentReceiverSetEventFromLog(log)); + } + + public Flowable paymentReceiverSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PAYMENTRECEIVERSET_EVENT)); + return paymentReceiverSetEventFlowable(filter); + } + + public Flowable pendingRefundProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPendingRefundProcessedEventFromLog(log)); + } + + public Flowable pendingRefundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PENDINGREFUNDPROCESSED_EVENT)); + return pendingRefundProcessedEventFlowable(filter); + } + + public Flowable refundAccountSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundAccountSetEventFromLog(log)); + } + + public Flowable refundAccountSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(REFUNDACCOUNTSET_EVENT)); + return refundAccountSetEventFlowable(filter); + } + + public Flowable refundProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundProcessedEventFromLog(log)); + } + + public Flowable refundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(REFUNDPROCESSED_EVENT)); + return refundProcessedEventFlowable(filter); + } + + public Flowable roleAdminChangedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleAdminChangedEventFromLog(log)); + } + + public Flowable roleAdminChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ROLEADMINCHANGED_EVENT)); + return roleAdminChangedEventFlowable(filter); + } + + public Flowable roleGrantedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleGrantedEventFromLog(log)); + } + + public Flowable roleGrantedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ROLEGRANTED_EVENT)); + return roleGrantedEventFlowable(filter); + } + + public Flowable roleRevokedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRoleRevokedEventFromLog(log)); + } + + public Flowable roleRevokedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ROLEREVOKED_EVENT)); + return roleRevokedEventFlowable(filter); + } + + public Flowable saveRefundProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSaveRefundProcessedEventFromLog(log)); + } + + public Flowable saveRefundProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(SAVEREFUNDPROCESSED_EVENT)); + return saveRefundProcessedEventFlowable(filter); + } + + public Flowable verifiedBalanceSetForEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceSetForEventFromLog(log)); + } + + public Flowable verifiedBalanceSetForEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCESETFOR_EVENT)); + return verifiedBalanceSetForEventFlowable(filter); + } + + public Flowable securityDelaySetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSecurityDelaySetEventFromLog(log)); + } + + public Flowable securityDelaySetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(SECURITYDELAYSET_EVENT)); + return securityDelaySetEventFlowable(filter); + } + + public Flowable settlementPeriodSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSettlementPeriodSetEventFromLog(log)); + } + + public Flowable settlementPeriodSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPERIODSET_EVENT)); + return settlementPeriodSetEventFlowable(filter); + } + + public Flowable settlementProcessedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getSettlementProcessedEventFromLog(log)); + } + + public Flowable settlementProcessedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(SETTLEMENTPROCESSED_EVENT)); + return settlementProcessedEventFlowable(filter); + } + + public Flowable verifiedBalanceIncreasedForEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedForEventFromLog(log)); + } + + public Flowable verifiedBalanceIncreasedForEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASEDFOR_EVENT)); + return verifiedBalanceIncreasedForEventFlowable(filter); + } + + public RemoteFunctionCall AUTHORIZATION_PROCESSOR_ROLE() { + final Function function = new Function(FUNC_AUTHORIZATION_PROCESSOR_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall BALANCE_VERIFIER_ROLE() { + final Function function = new Function(FUNC_BALANCE_VERIFIER_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall DEBT_PROCESSOR_ROLE() { + final Function function = new Function(FUNC_DEBT_PROCESSOR_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall DEFAULT_ADMIN_ROLE() { + final Function function = new Function(FUNC_DEFAULT_ADMIN_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall PAYMENT_ACCOUNT_SETTER() { + final Function function = new Function(FUNC_PAYMENT_ACCOUNT_SETTER, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall PROPERTY_SETTER_ROLE() { + final Function function = new Function(FUNC_PROPERTY_SETTER_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall REFUND_PROCESSOR_ROLE() { + final Function function = new Function(FUNC_REFUND_PROCESSOR_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall SETTLEMENT_PROCESSOR_ROLE() { + final Function function = new Function(FUNC_SETTLEMENT_PROCESSOR_ROLE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall fixedFee() { + final Function function = new Function(FUNC_FIXEDFEE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall getPaymentAccount(String card) { + final Function function = new Function(FUNC_GETPAYMENTACCOUNT, + List.of(new Address(160, card)), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall getRoleAdmin(byte[] role) { + final Function function = new Function(FUNC_GETROLEADMIN, + List.of(new Bytes32(role)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall getRoleMember(byte[] role, BigInteger index) { + final Function function = new Function(FUNC_GETROLEMEMBER, + Arrays.asList(new Bytes32(role), + new Uint256(index)), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall getRoleMemberCount(byte[] role) { + final Function function = new Function(FUNC_GETROLEMEMBERCOUNT, + List.of(new Bytes32(role)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall grantRole(byte[] role, String account) { + final Function function = new Function( + FUNC_GRANTROLE, + Arrays.asList(new Bytes32(role), + new Address(160, account)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall hasRole(byte[] role, String account) { + final Function function = new Function(FUNC_HASROLE, + Arrays.asList(new Bytes32(role), + new Address(160, account)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall increaseVerifiedBalanceFor(String paymentAccount, BigInteger increase) { + final Function function = new Function( + FUNC_INCREASEVERIFIEDBALANCEFOR, + Arrays.asList(new Address(160, paymentAccount), + new Uint256(increase)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall paymentAccountFactory() { + final Function function = new Function(FUNC_PAYMENTACCOUNTFACTORY, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall paymentReceiver() { + final Function function = new Function(FUNC_PAYMENTRECEIVER, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall paymentToken() { + final Function function = new Function(FUNC_PAYMENTTOKEN, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall processAuthorization(String card, BigInteger transactionId, BigInteger amount, byte[] otp, BigInteger otpCounter, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATION, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(amount), + new org.web3j.abi.datatypes.generated.Bytes16(otp), + new org.web3j.abi.datatypes.generated.Uint16(otpCounter), + new Bool(forced)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processAuthorizationChange(String card, BigInteger transactionId, BigInteger amount) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATIONCHANGE, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processAuthorizationNoOtp(String card, BigInteger transactionId, BigInteger amount, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATIONNOOTP, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(amount), + new Bool(forced)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processPendingRefund(String card, BigInteger transactionId) { + final Function function = new Function( + FUNC_PROCESSPENDINGREFUND, + Arrays.asList(new Address(160, card), + new Uint256(transactionId)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processRefund(String card, BigInteger transactionId, BigInteger refundAmount) { + final Function function = new Function( + FUNC_PROCESSREFUND, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(refundAmount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processRefundCallback() { + final Function function = new Function( + FUNC_PROCESSREFUNDCALLBACK, + List.of(), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processSettlement(String card, BigInteger transactionId, BigInteger settlementId, BigInteger amount) { + final Function function = new Function( + FUNC_PROCESSSETTLEMENT, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(settlementId), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall refundAccount() { + final Function function = new Function(FUNC_REFUNDACCOUNT, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall renounceRole(byte[] role, String callerConfirmation) { + final Function function = new Function( + FUNC_RENOUNCEROLE, + Arrays.asList(new Bytes32(role), + new Address(160, callerConfirmation)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall revokeRole(byte[] role, String account) { + final Function function = new Function( + FUNC_REVOKEROLE, + Arrays.asList(new Bytes32(role), + new Address(160, account)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall savePendingRefund(String card, BigInteger transactionId, BigInteger amount) { + final Function function = new Function( + FUNC_SAVEPENDINGREFUND, + Arrays.asList(new Address(160, card), + new Uint256(transactionId), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall securityDelay() { + final Function function = new Function(FUNC_SECURITYDELAY, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall setFixedFee(BigInteger fixedFee_) { + final Function function = new Function( + FUNC_SETFIXEDFEE, + List.of(new Uint256(fixedFee_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setPaymentAccount(String card, String paymentAccount) { + final Function function = new Function( + FUNC_SETPAYMENTACCOUNT, + Arrays.asList(new Address(160, card), + new Address(160, paymentAccount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setPaymentReceiver(String paymentReceiver_) { + final Function function = new Function( + FUNC_SETPAYMENTRECEIVER, + List.of(new Address(160, paymentReceiver_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setRefundAccount(String refundAccount_) { + final Function function = new Function( + FUNC_SETREFUNDACCOUNT, + List.of(new Address(160, refundAccount_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setSecurityDelay(BigInteger securityDelay_) { + final Function function = new Function( + FUNC_SETSECURITYDELAY, + List.of(new Uint256(securityDelay_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setSettlementPeriod(BigInteger settlementPeriod_) { + final Function function = new Function( + FUNC_SETSETTLEMENTPERIOD, + List.of(new Uint256(settlementPeriod_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setVerifiedBalanceFor(String paymentAccount, BigInteger verifiedBalance) { + final Function function = new Function( + FUNC_SETVERIFIEDBALANCEFOR, + Arrays.asList(new Address(160, paymentAccount), + new Uint256(verifiedBalance)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall settlementPeriod() { + final Function function = new Function(FUNC_SETTLEMENTPERIOD, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall supportsInterface(byte[] interfaceId) { + final Function function = new Function(FUNC_SUPPORTSINTERFACE, + List.of(new Bytes4(interfaceId)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall writeOffDebt(String card, BigInteger amount) { + final Function function = new Function( + FUNC_WRITEOFFDEBT, + Arrays.asList(new Address(160, card), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public static class AuthorizationChangeProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger amount; + + public BigInteger fee; + } + + public static class AuthorizationProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger amount; + + public BigInteger fee; + } + + public static class DebtWriteOffProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger amount; + } + + public static class FixedFeeSetEventResponse extends BaseEventResponse { + public BigInteger fixedFee; + } + + public static class PaymentAccountSetEventResponse extends BaseEventResponse { + public String card; + + public String paymentAccount; + } + + public static class PaymentReceiverSetEventResponse extends BaseEventResponse { + public String paymentReceiver; + } + + public static class PendingRefundProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class RefundAccountSetEventResponse extends BaseEventResponse { + public String refundAccount; + } + + public static class RefundProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class RoleAdminChangedEventResponse extends BaseEventResponse { + public byte[] role; + + public byte[] previousAdminRole; + + public byte[] newAdminRole; + } + + public static class RoleGrantedEventResponse extends BaseEventResponse { + public byte[] role; + + public String account; + + public String sender; + } + + public static class RoleRevokedEventResponse extends BaseEventResponse { + public byte[] role; + + public String account; + + public String sender; + } + + public static class SaveRefundProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class SecurityDelaySetEventResponse extends BaseEventResponse { + public BigInteger securityDelay; + } + + public static class SettlementPeriodSetEventResponse extends BaseEventResponse { + public BigInteger settlementPeriod; + } + + public static class SettlementProcessedEventResponse extends BaseEventResponse { + public String card; + + public BigInteger transactionId; + + public BigInteger settlementId; + + public BigInteger amount; + + public BigInteger fee; + } + + public static class VerifiedBalanceIncreasedForEventResponse extends BaseEventResponse { + public String paymentAccount; + + public BigInteger increase; + } + + public static class VerifiedBalanceSetForEventResponse extends BaseEventResponse { + public String paymentAccount; + + public BigInteger verifiedBalance; + } +} diff --git a/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java new file mode 100644 index 0000000000..943509e28e --- /dev/null +++ b/libs/visa/src/main/java/com/tangem/lib/visa/TangemPaymentAccount.java @@ -0,0 +1,1781 @@ +package com.tangem.lib.visa; + +import org.web3j.abi.EventEncoder; +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.*; +import org.web3j.abi.datatypes.generated.*; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.request.EthFilter; +import org.web3j.protocol.core.methods.response.BaseEventResponse; +import org.web3j.protocol.core.methods.response.Log; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tuples.generated.Tuple3; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import io.reactivex.Flowable; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 1.5.0. + */ +@SuppressWarnings("rawtypes") +class TangemPaymentAccount extends Contract { + public static final String BINARY = "60c06040523060a05234801562000014575f80fd5b5060405162004c6938038062004c69833981016040819052620000379162000109565b6001600160a01b0381166080526200004e62000055565b5062000138565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000a65760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620001065780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b5f602082840312156200011a575f80fd5b81516001600160a01b038116811462000131575f80fd5b9392505050565b60805160a051614af3620001765f395f8181612b9501528181612bbe0152612e1b01525f8181610439015281816104bb01526125500152614af35ff3fe60806040526004361061027f575f3560e01c80638c3b2a9b11610155578063ce1b1d43116100be578063e37b815411610078578063e37b8154146108c2578063e3dbffd51461092e578063e4d7ddb91461094d578063e6e268f41461096c578063f89db45714610980578063f92ad2191461099f575f80fd5b8063ce1b1d4314610831578063d47ae89c14610850578063dbed8faa14610864578063dcab7e5014610883578063df7727b4146102aa578063e1a8eafd146108a3575f80fd5b8063b4d02d0d1161010f578063b4d02d0d14610776578063b9603bdf14610795578063bb542ef0146107b4578063be69191d146107d3578063c45a0155146107e7578063c81656c814610806575f80fd5b80638c3b2a9b146106125780638cf351e7146106315780639335dcb714610650578063a075b7c71461066f578063a789457d1461068e578063ad3cb1cc14610739575f80fd5b8063456575e5116101f75780636b4943a7116101b15780636b4943a7146104985780637da0a877146104ad578063806679e4146104df578063860aefcf146104fe57806386f15d09146105df5780638bbe11af146105f3575f80fd5b8063456575e5146103c357806346236eb9146103e25780634f1ef286146103f657806352d1902d14610409578063572b6c051461041d5780636b2a383014610479575f80fd5b806322611280116102485780632261128014610308578063229865d11461031c5780633013ce291461033b57806334284dfb1461037157806335ba9af8146103905780633c1a5012146103a4575f80fd5b806274f3561461028357806301e948ff146102aa5780630f1071be146102be578063139d657c146102d257806319cdeb6f146102f3575b5f80fd5b34801561028e575f80fd5b506102976109be565b6040519081526020015b60405180910390f35b3480156102b5575f80fd5b506102976109d6565b3480156102c9575f80fd5b506102976109e5565b3480156102dd575f80fd5b506102f16102ec3660046140bc565b610a66565b005b3480156102fe575f80fd5b5061029760055481565b348015610313575f80fd5b506102f1610d5b565b348015610327575f80fd5b506102f16103363660046140e5565b610de8565b348015610346575f80fd5b505f54610359906001600160a01b031681565b6040516001600160a01b0390911681526020016102a1565b34801561037c575f80fd5b506102f161038b366004614136565b610e7f565b34801561039b575f80fd5b50610297610f45565b3480156103af575f80fd5b506102f16103be3660046141a1565b610fd4565b3480156103ce575f80fd5b506102f16103dd3660046141bc565b611060565b3480156103ed575f80fd5b50601b54610297565b6102f16104043660046141f0565b611100565b348015610414575f80fd5b5061029761111f565b348015610428575f80fd5b506104696104373660046141a1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b60405190151581526020016102a1565b348015610484575f80fd5b506102f16104933660046140e5565b61113a565b3480156104a3575f80fd5b5061029760065481565b3480156104b8575f80fd5b507f0000000000000000000000000000000000000000000000000000000000000000610359565b3480156104ea575f80fd5b506102f16104f93660046142ae565b611287565b348015610509575f80fd5b506040805160a08082018352600c54825282518084018452600d548152600e546020828101919091528084019190915283518085018552600f5481526010548183015283850152835180820185526011548152606080850191909152601254608080860191909152855193840186526013548452855180870187526014548152601554818501528484015285518087018752601654815260175481850152848701528551928301909552601854825282015260195492810192909252601a546105d0929083565b6040516102a19392919061432c565b3480156105ea575f80fd5b5061046961132a565b3480156105fe575f80fd5b506102f161060d3660046140e5565b611351565b34801561061d575f80fd5b506102f161062c366004614357565b611468565b34801561063c575f80fd5b506102f161064b366004614388565b611583565b34801561065b575f80fd5b50600354610359906001600160a01b031681565b34801561067a575f80fd5b506102f16106893660046143ca565b611710565b348015610699575f80fd5b506040805180820182526007546001600160a01b039081168252825180840184526008546001600160801b0319608082811b8216845261ffff600160801b938490048116602086810191909152808801959095528751808901895260095490961686528751808901909852600a549182901b9092168752919091041684820152810192909252600b5461072a929083565b6040516102a19392919061442a565b348015610744575f80fd5b50610769604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516102a19190614475565b348015610781575f80fd5b506102f16107903660046140e5565b611801565b3480156107a0575f80fd5b506102f16107af3660046140e5565b611901565b3480156107bf575f80fd5b506102f16107ce3660046141a1565b611a37565b3480156107de575f80fd5b50610469611b08565b3480156107f2575f80fd5b50600154610359906001600160a01b031681565b348015610811575f80fd5b506102976108203660046140e5565b5f908152601c602052604090205490565b34801561083c575f80fd5b50600254610359906001600160a01b031681565b34801561085b575f80fd5b50610297611b2d565b34801561086f575f80fd5b506102f161087e3660046140e5565b611bad565b34801561088e575f80fd5b505f8051602061479083398151915254610297565b3480156108ae575f80fd5b506102f16108bd3660046141a1565b611c0c565b3480156108cd575f80fd5b506109096108dc3660046140e5565b60046020525f9081526040902080546001909101546001600160801b03811690600160801b900460ff1683565b604080519384526001600160801b0390921660208401521515908201526060016102a1565b348015610939575f80fd5b506102f16109483660046140e5565b611da8565b348015610958575f80fd5b506102f16109673660046141bc565b611f82565b348015610977575f80fd5b5061029761212a565b34801561098b575f80fd5b506102f161099a3660046140e5565b61213f565b3480156109aa575f80fd5b506102f16109b93660046144a7565b61230d565b5f805f805160206147708339815191525b5492915050565b5f6109e05f61247c565b905090565b5f6109e060025f9054906101000a90046001600160a01b03166001600160a01b0316630f1071be6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a39573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5d91906144e7565b6283d600612534565b6002546001600160a01b0316610a7a61254d565b6001600160a01b0316146040518060600160405280603381526020016145b96033913990610ac45760405162461bcd60e51b8152600401610abb9190614475565b60405180910390fd5b50610ace8161259e565b5f8381526004602052604081205490818311610b00575081808203828214610afa57610afa86826125f0565b50610b7f565b50805f610b0d600c612634565b80549091505f81851015610b4157610b30858303610b2b855f61264e565b612534565b9050610b3e81610b2b6109d6565b90505b8015610b67575f610b5482878903612534565b948501949050610b6584825f6126b9565b505b838603868514610b7a57610b7a816126e0565b505050505b816006541015604051806060016040528060298152602001614a186029913990610bbc5760405162461bcd60e51b8152600401610abb9190614475565b506006805483900390555f85815260046020818152604080842093845560019390930180546001600160881b0319169055600254835163659bf9d960e11b81529351610c5d946001600160a01b039092169363cb37f3b29383820193909291908290030181865afa158015610c33573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c5791906144fe565b8261273f565b6040805184815260208101839052859187917f3e565e38537fb4064a62ad5c266c37a9798b673d92066b5bbbd2269c70078fd1910160405180910390a35f546040516370a0823160e01b81523060048201527f273f30c859762d267fd40559825ed2fa0f467908c92843dfd0184b9d577dc082916001600160a01b0316906370a0823190602401602060405180830381865afa158015610cff573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d2391906144e7565b600654610d2e6109be565b601b5460408051948552602085019390935291830152606082015260800160405180910390a15050505050565b6003546001600160a01b0316610d6f61254d565b6001600160a01b03161480610da65750610d89600761284a565b546001600160a01b0316610d9b61254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f913990610ddd5760405162461bcd60e51b8152600401610abb9190614475565b50610de6612864565b565b6003546001600160a01b0316610dfc61254d565b6001600160a01b03161480610e335750610e16600761284a565b546001600160a01b0316610e2861254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f913990610e6a5760405162461bcd60e51b8152600401610abb9190614475565b50610e7c81610e77611b2d565b6128b2565b50565b6002546001600160a01b0316610e9361254d565b6001600160a01b0316146040518060600160405280603381526020016145b96033913990610ed45760405162461bcd60e51b8152600401610abb9190614475565b506001610ee2848484612963565b610eee868684846129bf565b604080518681526001600160801b03198616602082015261ffff851681830152905187917faab1db26b8ce0ed14d67fd3d8eab92617f4caf4134148122ad6f2a4e20068df7919081900360600190a2505050505050565b5f80546040516370a0823160e01b815230600482015282916001600160a01b0316906370a0823190602401602060405180830381865afa158015610f8b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610faf91906144e7565b9050600654811015610fc1575f610fce565b600654610fce908261452d565b91505090565b6003546001600160a01b0316610fe861254d565b6001600160a01b0316148061101f5750611002600761284a565b546001600160a01b031661101461254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f9139906110565760405162461bcd60e51b8152600401610abb9190614475565b50610e7c81612a24565b6002546001600160a01b031661107461254d565b6001600160a01b0316146040518060600160405280603381526020016145b960339139906110b55760405162461bcd60e51b8152600401610abb9190614475565b506110c2601b8383612b0f565b817fea92e836c1b642a333bb58b44d5f13c5a066c5b0f35b8d6da5032af605c9e031826040516110f491815260200190565b60405180910390a25050565b611108612b8a565b61111182612c2e565b61111b8282612d54565b5050565b5f611128612e10565b505f8051602061484383398151915290565b6002546001600160a01b031661114e61254d565b6001600160a01b0316146040518060600160405280603381526020016145b9603391399061118f5760405162461bcd60e51b8152600401610abb9190614475565b505f8160055461119f9190614540565b5f546040516370a0823160e01b81523060048201529192506001600160a01b0316906370a0823190602401602060405180830381865afa1580156111e5573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061120991906144e7565b81111560405180608001604052806048815260200161468d60489139906112435760405162461bcd60e51b8152600401610abb9190614475565b50600581905560408051838152602081018390527fd6004694c2f543b69302481de26731f63c1d41062795b5366d8ca20fd3e3d6d391015b60405180910390a15050565b6002546001600160a01b031661129b61254d565b6001600160a01b0316146040518060600160405280603381526020016145b960339139906112dc5760405162461bcd60e51b8152600401610abb9190614475565b505f6112ea848484846129bf565b837f5e1266400272ee17509f7d7edb190c0fa83572f90677ee6a3bbfac22bf1f679d8460405161131c91815260200190565b60405180910390a250505050565b5f80516020614790833981519152545f905f80516020614730833981519152901515610fce565b5f818152600460205260409020600181015490546001600160801b03909116906113796109e5565b6113839083614540565b42116040518060600160405280602b81526020016145ec602b9139906113bc5760405162461bcd60e51b8152600401610abb9190614475565b50806006541015604051806060016040528060298152602001614a1860299139906113fa5760405162461bcd60e51b8152600401610abb9190614475565b506006805482900390555f8381526004602052604080822091825560019190910180546001600160881b03191690555183907f056bed1bad5d90aa1df671c8d28b1bbb82ff67fd545f804e63cd261140b11f7b9061145b9084815260200190565b60405180910390a2505050565b6003546001600160a01b031661147c61254d565b6001600160a01b031614806114b35750611496600761284a565b546001600160a01b03166114a861254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f9139906114ea5760405162461bcd60e51b8152600401610abb9190614475565b506040805180820182526009546001600160a01b0316815281518083019092526001600160801b03198416825261ffff831660208381019190915281019190915261154090611537611b2d565b60079190612e59565b604080516001600160801b03198416815261ffff831660208201527fc50d2cc66f62a75104edeabfbcedb2a37ebd1cb8ce0b873ec3afa504e8bb1480910161127b565b6003546001600160a01b031661159761254d565b6001600160a01b031614806115ce57506115b1600761284a565b546001600160a01b03166115c361254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f9139906116055760405162461bcd60e51b8152600401610abb9190614475565b50600b541561165e576116596040518060400160405280856001600160a01b031681526020016040518060400160405280866001600160801b03191681526020018561ffff16815250815250611537611b2d565b6116b1565b6116b16040518060400160405280856001600160a01b031681526020016040518060400160405280866001600160801b03191681526020018561ffff16815250815250600761300090919063ffffffff16565b604080516001600160a01b03851681526001600160801b03198416602082015261ffff8316918101919091527fae81d31085749debb22edb9a09f263e50cf6047cfc57ec741c07ce62b1121b9a906060015b60405180910390a1505050565b6003546001600160a01b031661172461254d565b6001600160a01b0316148061175b575061173e600761284a565b546001600160a01b031661175061254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f9139906117925760405162461bcd60e51b8152600401610abb9190614475565b506117b36117a2858585856130a1565b6117aa611b2d565b600c9190613102565b6040805185815260208101859052908101839052606081018290527ffc3ccf3ca5a38fad8786ed39386de19cb574b06205422992c583e4753aa6268f9060800160405180910390a150505050565b6003546001600160a01b031661181561254d565b6001600160a01b0316148061184c575061182f600761284a565b546001600160a01b031661184161254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f9139906118835760405162461bcd60e51b8152600401610abb9190614475565b50610e7c8160025f9054906101000a90046001600160a01b03166001600160a01b031663cb37f3b26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118d8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118fc91906144fe565b6131f0565b6002546001600160a01b031661191561254d565b6001600160a01b0316146040518060600160405280603381526020016145b960339139906119565760405162461bcd60e51b8152600401610abb9190614475565b505f546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa15801561199c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119c091906144e7565b81111560405180608001604052806048815260200161468d60489139906119fa5760405162461bcd60e51b8152600401610abb9190614475565b5060058190556040518181527f321b800465e47508430424e6c089344256e17f9a5b0b43c751dffbdfd3930263906020015b60405180910390a150565b6003546001600160a01b0316611a4b61254d565b6001600160a01b03161480611a825750611a65600761284a565b546001600160a01b0316611a7761254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f913990611ab95760405162461bcd60e51b8152600401610abb9190614475565b50600380546001600160a01b0319166001600160a01b0383169081179091556040519081527f0435c4db9db56b8cdd5d58a213f47d6fed6c2b35c81bbd6baf87547848f4c88b90602001611a2c565b5f5f80516020614730833981519152610fce5f805160206147908339815191526133a4565b5f6109e060025f9054906101000a90046001600160a01b03166001600160a01b031663d47ae89c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b81573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ba591906144e7565b610e10612534565b6002546001600160a01b0316611bc161254d565b6001600160a01b0316146040518060600160405280603381526020016145b96033913990611c025760405162461bcd60e51b8152600401610abb9190614475565b50610e7c8161340b565b6003546001600160a01b0316611c2061254d565b6001600160a01b03161480611c575750611c3a600761284a565b546001600160a01b0316611c4c61254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f913990611c8e5760405162461bcd60e51b8152600401610abb9190614475565b50600254604080516060810190915260238082526001600160a01b039092161591614641602083013990611cd55760405162461bcd60e51b8152600401610abb9190614475565b50600280546001600160a01b0319166001600160a01b03831690811790915560408051633013ce2960e01b81529051633013ce29916004808201926020929091908290030181865afa158015611d2d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d5191906144fe565b5f80546001600160a01b0319166001600160a01b0392831690811790915560408051928416835260208301919091527fb84a8bf26086331c9421c7ce6650da3b0ef748bd335c3f00ada56f481e4321939101611a2c565b6002546001600160a01b0316611dbc61254d565b6001600160a01b0316146040518060600160405280603381526020016145b96033913990611dfd5760405162461bcd60e51b8152600401610abb9190614475565b505f80546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015611e44573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e6891906144e7565b905060025f9054906101000a90046001600160a01b03166001600160a01b0316632cc326416040518163ffffffff1660e01b81526004015f604051808303815f87803b158015611eb6575f80fd5b505af1158015611ec8573d5f803e3d5ffd5b50505f80546040516370a0823160e01b81523060048201529193508492506001600160a01b0316906370a0823190602401602060405180830381865afa158015611f14573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f3891906144e7565b611f42919061452d565b9050611f50601b84836134e9565b827f2c6a6ee02f179bb5f13a4b7fdc9ccd1dd37e524b48ff0957a931348cc9ad75c08260405161145b91815260200190565b6002546001600160a01b0316611f9661254d565b6001600160a01b0316146040518060600160405280603381526020016145b96033913990611fd75760405162461bcd60e51b8152600401610abb9190614475565b505f60045f8481526020019081526020015f205f015490505f811160405180606001604052806023815260200161470d60239139906120295760405162461bcd60e51b8152600401610abb9190614475565b508082106040518060600160405280603b8152602001614929603b9139906120645760405162461bcd60e51b8152600401610abb9190614475565b5081810361207284826125f0565b806006541015604051806060016040528060298152602001614a1860299139906120af5760405162461bcd60e51b8152600401610abb9190614475565b506006805482900390555f8390036120e7575f84815260046020526040812090815560010180546001600160881b03191690556120f8565b5f8481526004602052604090208390555b837f8274e5392b3ab6bba68ee0567f4ede55e361239b8d0122fa5d3b5dc36497b91d8460405161131c91815260200190565b5f805f805160206147308339815191526109cf565b6002546001600160a01b031661215361254d565b6001600160a01b0316146040518060600160405280603381526020016145b960339139906121945760405162461bcd60e51b8152600401610abb9190614475565b505f80546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa1580156121db573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121ff91906144e7565b905060025f9054906101000a90046001600160a01b03166001600160a01b0316632cc326416040518163ffffffff1660e01b81526004015f604051808303815f87803b15801561224d575f80fd5b505af115801561225f573d5f803e3d5ffd5b50505f80546040516370a0823160e01b81523060048201529193508492506001600160a01b0316906370a0823190602401602060405180830381865afa1580156122ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122cf91906144e7565b6122d9919061452d565b9050827f2c6a6ee02f179bb5f13a4b7fdc9ccd1dd37e524b48ff0957a931348cc9ad75c08260405161145b91815260200190565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156123525750825b90505f8267ffffffffffffffff16600114801561236e5750303b155b90508115801561237c575080155b1561239a5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156123c457845460ff60401b1916600160401b1785555b6123cc6135ce565b6123d46135ce565b6123dc6135ce565b6123e461254d565b600180546001600160a01b03199081166001600160a01b039384161790915560038054909116918c1691909117905561242a6124228a8a8a8a6130a1565b600c906135d6565b831561247057845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050505050565b5f80546040516370a0823160e01b815230600482015282916124f4916001600160a01b03909116906370a0823190602401602060405180830381865afa1580156124c8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124ec91906144e7565b600554612534565b600654909150836125145761250761212a565b6125119082614540565b90505b80821015612522575f61252c565b61252c818361452d565b949350505050565b5f8183106125425781612544565b825b90505b92915050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633148015612587575060143610155b15612599575060131936013560601c90565b503390565b60408051808201909152601b81527f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f000000000060208201528161111b5760405162461bcd60e51b8152600401610abb9190614475565b5f828152600460205260409020600101546001600160801b03811690600160801b900460ff1661262e838383612626600c612634565b929190613662565b50505050565b5f4282600e015411156126475781612547565b5060070190565b5f61265b836005016133a4565b1561268c57811561267157506001820154612547565b600183015460038401546126859190612534565b9050612547565b811561269e5761268583600101613690565b6126856126ad84600101613690565b610b2b85600301613690565b6126c2836136b0565b6126cc83836136e0565b806126db576126db83836136ed565b505050565b5f805160206147708339815191528054829082905f90612701908490614540565b909155505080546040805184815260208101929092527ff3a2c404b3362c4238317c3dad1020c2e9e984ba8df38a4a1ff18d57e92a242a910161127b565b5f80546040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015612785573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127a991906144e7565b905080600554146040518060600160405280603381526020016149b760339139906127e75760405162461bcd60e51b8152600401610abb9190614475565b5060408051808201909152601f81525f8051602061475083398151915260208201528282101561282a5760405162461bcd60e51b8152600401610abb9190614475565b506005805483900390555f546126db906001600160a01b031684846136fa565b5f428260040154111561285d5781612547565b5060020190565b5f5f805160206147308339815191528181555f8051602061479083398151915282905560405190917f8892a543ee6d561b73cc067b2cde5a76c251a4e0b978d92390f1301db5d125ab91a150565b6128ba610f45565b8211156040518060400160405280601f81526020015f80516020614750833981519152815250906128fe5760405162461bcd60e51b8152600401610abb9190614475565b505f805160206147308339815191528281556129275f805160206147908339815191528361374c565b60018101546040805185815260208101929092527ffa4870dc2e2f95d11863e7e765a98e12fe92c3d1d2a703b4cf983f9c4b1052e69101611703565b600b54421015806129715750805b6040518060600160405280603581526020016148cd60359139906129a85760405162461bcd60e51b8152600401610abb9190614475565b506126db83836129b8600761284a565b91906137af565b60045f8581526020019081526020015f205f01545f1460405180606001604052806029815260200161498e6029913990612a0c5760405162461bcd60e51b8152600401610abb9190614475565b50612a188383836137ca565b61262e8484848461380b565b5f805160206147308339815191528054612a3c610f45565b8111156040518060400160405280601f81526020015f8051602061475083398151915281525090612a805760405162461bcd60e51b8152600401610abb9190614475565b50612a89611b08565b6040518060600160405280603881526020016146d56038913990612ac05760405162461bcd60e51b8152600401610abb9190614475565b505f8083556001830155612ad483826139a6565b826001600160a01b03167fa578b4c05763eb039caef37b2d7b949c22914c3838384c9c73cefb78c4a8ed0a8260405161145b91815260200190565b826001015f8381526020019081526020015f20545f146040518060600160405280602a8152602001614617602a913990612b5c5760405162461bcd60e51b8152600401610abb9190614475565b505f8281526001840160205260408120829055835482918591612b80908490614540565b9091555050505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480612c1057507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316612c045f80516020614843833981519152546001600160a01b031690565b6001600160a01b031614155b15610de65760405163703e46dd60e11b815260040160405180910390fd5b6003546001600160a01b0316612c4261254d565b6001600160a01b03161480612c795750612c5c600761284a565b546001600160a01b0316612c6e61254d565b6001600160a01b0316145b6040518060600160405280602f8152602001614a66602f913990612cb05760405162461bcd60e51b8152600401610abb9190614475565b506001546040516316da1fb760e11b81526001600160a01b03838116600483015290911690632db43f6e90602401602060405180830381865afa158015612cf9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d1d9190614553565b604051806060016040528060298152602001614a95602991399061111b5760405162461bcd60e51b8152600401610abb9190614475565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612dae575060408051601f3d908101601f19168201909252612dab918101906144e7565b60015b612dd657604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610abb565b5f805160206148438339815191528114612e0657604051632a87526960e21b815260048101829052602401610abb565b6126db83836139dd565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610de65760405163703e46dd60e11b815260040160405180910390fd5b602082015151600384015460801b6001600160801b03199081169116141580612e9c575060208083015101516003840154600160801b900461ffff908116911614155b80612eb95750815160028401546001600160a01b03908116911614155b6040518060600160405280602781526020016149026027913990612ef05760405162461bcd60e51b8152600401610abb9190614475565b506004830154421015612f695781516002840180546001600160a01b039092166001600160a01b03199092169190911790556020808301518051600386018054929093015161ffff16600160801b026001600160901b031990921660809190911c17179055612f5f8142614540565b6004840155505050565b60028301805484546001600160a01b03199081166001600160a01b0380841691909117875560038701805460018901805461ffff600160801b808504821681026001600160901b03199384166001600160801b03871617179093558a51909516959096169490941790955560208088015180519101519092169092029290931660809390931c92909217179055612f5f8142614540565b60048201541515156040518060600160405280602f815260200161458a602f91399061303f5760405162461bcd60e51b8152600401610abb9190614475565b5080516002830180546001600160a01b039092166001600160a01b03199092169190911790556020908101518051600384018054929093015161ffff16600160801b026001600160901b031990921660809190911c1717905542600490910155565b6130a961404a565b506040805160a081018252948552805180820182529384525f6020858101829052808701959095528151808301835293845283850152848101929092528151928301909152600182526060830191909152608082015290565b600e830154421015613166578151600784015560208083015180516008860155810151600985015560408301518051600a8601550151600b840155606082015151600c8401556080820151600d84015561315c8142614540565b600e840155505050565b600783018054845560088401805460018601556009850180546002870155600a860180546003880155600b870180546004890155600c8801805460058a0155600d8901805460068b015588519096556020808901518051909655948501519093556040870151805190925592015190915560608401515190556080830151905561315c8142614540565b5f805160206147708339815191525f6132076109d6565b825460408051808201909152601b81527f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f00000000006020820152919250908561325f5760405162461bcd60e51b8152600401610abb9190614475565b5060408051808201909152601881527f353430317c446562743a206e6f206465627420666f756e6400000000000000006020820152816132b25760405162461bcd60e51b8152600401610abb9190614475565b5060408051808201909152601d81527f353430327c446562743a206e6f2066756e647320617661696c61626c650000006020820152826133055760405162461bcd60e51b8152600401610abb9190614475565b50808511156040518060600160405280602581526020016147d560259139906133415760405162461bcd60e51b8152600401610abb9190614475565b505f61334d8387612534565b8454819003855590506133608582613a32565b83546040805183815260208101929092527f75aec94f6ad949cfd9728eb818639f1b0b882089b480409ab73245c06469eef4910160405180910390a1505050505050565b5f6133af8254151590565b6040518060400160405280601d81526020017f353534317c54696d6572733a2074696d6572206e6f7420616374697665000000815250906134035760405162461bcd60e51b8152600401610abb9190614475565b505054421190565b5f80516020614770833981519152805460408051808201909152601b81527f353130307c436f6d6d6f6e3a20616d6f756e74206973207a65726f000000000060208201528361346d5760405162461bcd60e51b8152600401610abb9190614475565b50808311156040518060600160405280602581526020016147d560259139906134a95760405162461bcd60e51b8152600401610abb9190614475565b5081548390038083556040805185815260208101929092527fb940258db5193d85cafbc591d0c29fad88d804ff8b9ab71c8f9545af2bb2c07c9101611703565b5f836001015f8481526020019081526020015f205490505f81116040518060600160405280602581526020016147b0602591399061353a5760405162461bcd60e51b8152600401610abb9190614475565b50808214604051806060016040528060258152602001614a4160259139906135755760405162461bcd60e51b8152600401610abb9190614475565b50835f01548111156040518060600160405280602a8152602001614964602a9139906135b45760405162461bcd60e51b8152600401610abb9190614475565b508354038355505f90815260019091016020526040812055565b610de6613a3c565b600e8201541515156040518060600160405280602f815260200161458a602f9139906136155760405162461bcd60e51b8152600401610abb9190614475565b508051600783015560208082015180516008850155810151600984015560408201518051600a8501550151600b830155606081015151600c83015560800151600d82015542600e90910155565b61366c8483613a85565b1561262e5761367e6001850184613ac0565b8061262e5761262e6003850184613ac0565b805460018201545f9190808210156136a8575f61252c565b900392915050565b6136bc816005016133a4565b15610e7c575f60028201555f60048201556006810154610e7c90600583019061374c565b61111b6001830182613b0f565b61111b6003830182613b0f565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526126db908490613b2b565b60408051808201909152601781527f353534307c54696d6572733a207a65726f2064656c617900000000000000000060208201528161379e5760405162461bcd60e51b8152600401610abb9190614475565b506137a98142614540565b90915550565b82546126db9060018501906001600160a01b03168484613b8c565b601a54421015806137d85750815b156137f3576126db83826137ec600c612634565b9190613d12565b6137ff600c8483613d12565b6126db60138483613d12565b6138148361259e565b5f821561387d575f613826600161247c565b905084811061383757849150613877565b809150857f83067e3478aa8b638f7e34eeecb23875cc3671e615445e892cc22dcd4c8b8df382870360405161386e91815260200190565b60405180910390a25b506138ce565b6138856109d6565b8411156040518060400160405280601f81526020015f80516020614750833981519152815250906138c95760405162461bcd60e51b8152600401610abb9190614475565b508390505b60408051808201909152601f81527f353331337c4163636f756e743a206e6f2066756e647320746f20626c6f636b006020820152816139205760405162461bcd60e51b8152600401610abb9190614475565b50604080516060810182528281526001600160801b0342811660208084019182528615158486019081525f8b815260049092529481209351845590516001909301805494511515600160801b026001600160881b03199095169390921692909217929092179091556006805483929061399a908490614540565b90915550505050505050565b8060055410156139b6575f6139c4565b806005546139c4919061452d565b6005555f5461111b906001600160a01b031683836136fa565b6139e682613d73565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115613a2a576126db8282613dd6565b61111b613e48565b61111b828261273f565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610de657604051631afcd79f60e31b815260040160405180910390fd5b5f60058301613a93816133a4565b15613aa1575f915050612547565b600684015481545f91613ab39161452d565b8410159250612547915050565b80826001015410156040518060600160405280602e81526020016149ea602e913990613aff5760405162461bcd60e51b8152600401610abb9190614475565b5060019091018054919091039055565b80826001015f828254613b229190614540565b90915550505050565b5f613b3f6001600160a01b03841683613e67565b905080515f14158015613b63575080806020019051810190613b619190614553565b155b156126db57604051635274afe760e01b81526001600160a01b0384166004820152602401610abb565b83546040805160608101909152602980825261ffff600160801b909304831692841683119190614664602083013990613bd85760405162461bcd60e51b8152600401610abb9190614475565b5082825b8261ffff168161ffff161015613c94576040516bffffffffffffffffffffffff19606088901b1660208201526001600160f01b031960f083901b1660348201526001600160801b03198316603682015260029060460160408051601f1981840301815290829052613c4c9161456e565b602060405180830381855afa158015613c67573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190613c8a91906144e7565b9150600101613bdc565b5085546040805160608101909152602180825260809290921b6001600160801b03199081169084161491614822602083013990613ce45760405162461bcd60e51b8152600401610abb9190614475565b5050845461ffff909216600160801b026001600160901b031990921660809390931c92909217179092555050565b825f01548211156040518060600160405280603b8152602001614892603b913990613d505760405162461bcd60e51b8152600401610abb9190614475565b50613d5a836136b0565b613d648383613e74565b806126db576126db8383613ec3565b806001600160a01b03163b5f03613da857604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610abb565b5f8051602061484383398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b031684604051613df2919061456e565b5f60405180830381855af49150503d805f8114613e2a576040519150601f19603f3d011682016040523d82523d5f602084013e613e2f565b606091505b5091509150613e3f858383613f15565b95945050505050565b3415610de65760405163b398979f60e01b815260040160405180910390fd5b606061254483835f613f74565b613e81600183018261400d565b6040518060600160405280602881526020016147fa6028913990613eb85760405162461bcd60e51b8152600401610abb9190614475565b5061111b82826136e0565b613ed0600383018261400d565b6040518060600160405280602f8152602001614863602f913990613f075760405162461bcd60e51b8152600401610abb9190614475565b5061111b6003830182613b0f565b606082613f2a57613f2582614021565b613f6d565b8151158015613f4157506001600160a01b0384163b155b15613f6a57604051639996b31560e01b81526001600160a01b0385166004820152602401610abb565b50805b9392505050565b606081471015613f995760405163cd78605960e01b8152306004820152602401610abb565b5f80856001600160a01b03168486604051613fb4919061456e565b5f6040518083038185875af1925050503d805f8114613fee576040519150601f19603f3d011682016040523d82523d5f602084013e613ff3565b606091505b5091509150614003868383613f15565b9695505050505050565b5f61401783613690565b9091111592915050565b8051156140315780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6040518060a001604052805f815260200161407660405180604001604052805f81526020015f81525090565b815260200161409660405180604001604052805f81526020015f81525090565b81526020016140b060405180602001604052805f81525090565b81526020015f81525090565b5f805f606084860312156140ce575f80fd5b505081359360208301359350604090920135919050565b5f602082840312156140f5575f80fd5b5035919050565b80356001600160801b031981168114614113575f80fd5b919050565b803561ffff81168114614113575f80fd5b8015158114610e7c575f80fd5b5f805f805f60a0868803121561414a575f80fd5b8535945060208601359350614161604087016140fc565b925061416f60608701614118565b9150608086013561417f81614129565b809150509295509295909350565b6001600160a01b0381168114610e7c575f80fd5b5f602082840312156141b1575f80fd5b8135613f6d8161418d565b5f80604083850312156141cd575f80fd5b50508035926020909101359150565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215614201575f80fd5b823561420c8161418d565b9150602083013567ffffffffffffffff80821115614228575f80fd5b818501915085601f83011261423b575f80fd5b81358181111561424d5761424d6141dc565b604051601f8201601f19908116603f01168101908382118183101715614275576142756141dc565b8160405282815288602084870101111561428d575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f805f606084860312156142c0575f80fd5b833592506020840135915060408401356142d981614129565b809150509250925092565b8051825260208082015180518285015290810151604084015250604081015180516060840152602081015160808401525060608101515160a08301526080015160c090910152565b6101e0810161433b82866142e4565b61434860e08301856142e4565b826101c0830152949350505050565b5f8060408385031215614368575f80fd5b614371836140fc565b915061437f60208401614118565b90509250929050565b5f805f6060848603121561439a575f80fd5b83356143a58161418d565b92506143b3602085016140fc565b91506143c160408501614118565b90509250925092565b5f805f80608085870312156143dd575f80fd5b5050823594602084013594506040840135936060013592509050565b80516001600160a01b0316825260209081015180516001600160801b03191682840152015161ffff16604090910152565b60e0810161443882866143f9565b61444560608301856143f9565b8260c0830152949350505050565b5f5b8381101561446d578181015183820152602001614455565b50505f910152565b602081525f8251806020840152614493816040850160208701614453565b601f01601f19169190910160400192915050565b5f805f805f60a086880312156144bb575f80fd5b85356144c68161418d565b97602087013597506040870135966060810135965060800135945092505050565b5f602082840312156144f7575f80fd5b5051919050565b5f6020828403121561450e575f80fd5b8151613f6d8161418d565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561254757612547614519565b8082018082111561254757612547614519565b5f60208284031215614563575f80fd5b8151613f6d81614129565b5f825161457f818460208701614453565b919091019291505056fe353530307c44656c6179656453657474696e67733a2076616c756520616c726561647920696e697469616c697a6564353330327c4163636f756e743a206f6e6c792070726f636573736f722063616e2063616c6c20746869732066756e6374696f6e353334307c4163636f756e743a20736574746c656d656e7420706572696f64206973206e6f74206f766572353432307c526566756e64733a20726566756e64207265636f726420616c726561647920657869737473353336307c4163636f756e743a2070726f636573736f7220616c726561647920736574353531307c4f6e6554696d6550617373776f72643a20696e76616c6964204f545020636f756e746572353335307c4163636f756e743a20726573756c74696e672076657269666965642062616c616e6365206e6f742065717569616c20746f2062616c616e636520616e64206e6f742030353431307c44656c617965645769746864726177616c733a207769746864726177616c2074696d656c6f636b206e6f742065787072696564353332307c4163636f756e743a207472616e73616374696f6e206e6f7420666f756e6476d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435500353130317c436f6d6d6f6e3a20696e73756666696369656e742066756e6473003486e95df892f06bd37fd38c44e2fee4c4efb6660a66e6eb20ed6d8a167eae0076d8021c4b979e99674b80f4e0cc10154af0e5f32df05eb51b29a464e2435501353432317c526566756e64733a20726566756e64207265636f7264206e6f7420666f756e64353430337c446562743a20616d6f756e7420697320686967686572207468616e2064656274353532317c4163636f756e744c696d6974733a207370656e64206c696d6974206578636565646564353531317c4f6e6554696d6550617373776f72643a20696e76616c6964204f5450360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc353532337c4163636f756e744c696d6974733a206e6f206f7470207370656e64206c696d6974206578636565646564353532307c4163636f756e744c696d6974733a20616d6f756e7420657863656564732073696e676c65207472616e73616374696f6e206c696d6974353331327c4163636f756e743a2063617264206f72204f5450207374617465206368616e676520697320696e2070726f6772657373353530317c44656c6179656453657474696e67733a2076616c756520616c726561647920736574353332317c4163636f756e743a206e657720616d6f756e7420697320657175616c206f7220686967686572207468616e20617574686f72697a6564353432337c526566756e64733a2070656e64696e6720616d6f756e74206578636565647320746f74616c353331307c4163636f756e743a207472616e73616374696f6e20494420616c72656164792075736564353333307c4163636f756e743a2076657269666965642062616c616e6365206e6f7420657175616c20746f2062616c616e6365353533317c5370656e644c696d6974733a20696e73756666696369656e74207370656e7420746f2063616e63656c353330347c4163636f756e743a20696e73756666696369656e7420626c6f636b656420616d6f756e74353432327c526566756e64733a20696e76616c696420616d6f756e74207265636569766564353330317c4163636f756e743a206f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e353337307c4163636f756e743a20756e617574686f72697a656420696d706c656d656e746174696f6ea264697066735822122012a8bf0b550c0636d300948851d60c701346e82b5d29b544481d654d6f3ba1f164736f6c63430008160033"; + + public static final String FUNC_UPGRADE_INTERFACE_VERSION = "UPGRADE_INTERFACE_VERSION"; + + public static final String FUNC_AUTHORIZEDTRANSACTIONS = "authorizedTransactions"; + + public static final String FUNC_AVAILABLEFORDEBTPAYMENT = "availableForDebtPayment"; + + public static final String FUNC_AVAILABLEFORPAYMENT = "availableForPayment"; + + public static final String FUNC_AVAILABLEFORWITHDRAWAL = "availableForWithdrawal"; + + public static final String FUNC_BLOCKEDAMOUNT = "blockedAmount"; + + public static final String FUNC_CANCELWITHDRAWAL = "cancelWithdrawal"; + + public static final String FUNC_CARDWITHOTP = "cardWithOtp"; + + public static final String FUNC_DEBTAMOUNT = "debtAmount"; + + public static final String FUNC_FACTORY = "factory"; + + public static final String FUNC_INCREASEVERIFIEDBALANCE = "increaseVerifiedBalance"; + + public static final String FUNC_INITWITHDRAWAL = "initWithdrawal"; + + public static final String FUNC_INITIALIZE = "initialize"; + + public static final String FUNC_ISTRUSTEDFORWARDER = "isTrustedForwarder"; + + public static final String FUNC_ISWITHDRAWALINPROGRESS = "isWithdrawalInProgress"; + + public static final String FUNC_ISWITHDRAWALREADY = "isWithdrawalReady"; + + public static final String FUNC_LIMITS = "limits"; + + public static final String FUNC_OWNERWALLET = "ownerWallet"; + + public static final String FUNC_PAYMENTTOKEN = "paymentToken"; + + public static final String FUNC_PENDINGREFUNDAMOUNT = "pendingRefundAmount"; + + public static final String FUNC_PENDINGREFUNDTOTAL = "pendingRefundTotal"; + + public static final String FUNC_PROCESSAUTHORIZATION = "processAuthorization"; + + public static final String FUNC_PROCESSAUTHORIZATIONCHANGE = "processAuthorizationChange"; + + public static final String FUNC_PROCESSAUTHORIZATIONNOOTP = "processAuthorizationNoOtp"; + + public static final String FUNC_PROCESSDEBT = "processDebt"; + + public static final String FUNC_PROCESSPENDINGREFUNDPAYMENT = "processPendingRefundPayment"; + + public static final String FUNC_PROCESSREFUNDPAYMENT = "processRefundPayment"; + + public static final String FUNC_PROCESSSETTLEMENT = "processSettlement"; + + public static final String FUNC_PROCESSWITHDRAWAL = "processWithdrawal"; + + public static final String FUNC_PROCESSOR = "processor"; + + public static final String FUNC_PROXIABLEUUID = "proxiableUUID"; + + public static final String FUNC_SAVEPENDINGREFUND = "savePendingRefund"; + + public static final String FUNC_SECURITYDELAY = "securityDelay"; + + public static final String FUNC_SETCARD = "setCard"; + + public static final String FUNC_SETLIMITS = "setLimits"; + + public static final String FUNC_SETOTPSTATE = "setOtpState"; + + public static final String FUNC_SETOWNERWALLET = "setOwnerWallet"; + + public static final String FUNC_SETPROCESSOR = "setProcessor"; + + public static final String FUNC_SETVERIFIEDBALANCE = "setVerifiedBalance"; + + public static final String FUNC_SETTLEMENTPERIOD = "settlementPeriod"; + + public static final String FUNC_TRUSTEDFORWARDER = "trustedForwarder"; + + public static final String FUNC_UNBLOCKUNSETTLEDTRANSACTION = "unblockUnsettledTransaction"; + + public static final String FUNC_UPGRADETOANDCALL = "upgradeToAndCall"; + + public static final String FUNC_VERIFIEDBALANCE = "verifiedBalance"; + + public static final String FUNC_WITHDRAWALAMOUNT = "withdrawalAmount"; + + public static final String FUNC_WITHDRAWALREADYTIMESTAMP = "withdrawalReadyTimestamp"; + + public static final String FUNC_WRITEOFFDEBT = "writeOffDebt"; + + public static final Event ACCOUNTSTATEAFTERSETTLEMENT_EVENT = new Event("AccountStateAfterSettlement", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event CARDSET_EVENT = new Event("CardSet", + Arrays.asList(new TypeReference

() { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event DEBTINCREASED_EVENT = new Event("DebtIncreased", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + public static final Event DEBTPAID_EVENT = new Event("DebtPaid", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + public static final Event DEBTWRITTENOFF_EVENT = new Event("DebtWrittenOff", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + public static final Event INITIALIZED_EVENT = new Event("Initialized", + List.of(new TypeReference() { + })); + + public static final Event INSUFFICIENTFUNDSONFORCEDAUTH_EVENT = new Event("InsufficientFundsOnForcedAuth", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event LIMITSSET_EVENT = new Event("LimitsSet", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event NOOTPTRANSACTIONAUTHORIZED_EVENT = new Event("NoOtpTransactionAuthorized", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event OTPSTATESET_EVENT = new Event("OtpStateSet", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + public static final Event OWNERWALLETSET_EVENT = new Event("OwnerWalletSet", + List.of(new TypeReference
() { + })); + + public static final Event PENDINGREFUNDSAVED_EVENT = new Event("PendingRefundSaved", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event PROCESSORSET_EVENT = new Event("ProcessorSet", + Arrays.asList(new TypeReference
() { + }, new TypeReference
() { + })); + + public static final Event REFUNDPAID_EVENT = new Event("RefundPaid", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event TRANSACTIONAMOUNTCHANGED_EVENT = new Event("TransactionAmountChanged", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event TRANSACTIONAUTHORIZED_EVENT = new Event("TransactionAuthorized", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event TRANSACTIONSETTLED_EVENT = new Event("TransactionSettled", + Arrays.asList(new TypeReference(true) { + }, new TypeReference(true) { + }, new TypeReference() { + }, new TypeReference() { + })); + + public static final Event UNSETTLEDTRANSACTIONUNBLOCKED_EVENT = new Event("UnsettledTransactionUnblocked", + Arrays.asList(new TypeReference(true) { + }, new TypeReference() { + })); + + public static final Event UPGRADED_EVENT = new Event("Upgraded", + List.of(new TypeReference
(true) { + })); + + public static final Event VERIFIEDBALANCEINCREASED_EVENT = new Event("VerifiedBalanceIncreased", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + public static final Event VERIFIEDBALANCESET_EVENT = new Event("VerifiedBalanceSet", + List.of(new TypeReference() { + })); + + public static final Event WITHDRAWALCANCELED_EVENT = new Event("WithdrawalCanceled", + List.of()); + + public static final Event WITHDRAWALCOMPLETE_EVENT = new Event("WithdrawalComplete", + Arrays.asList(new TypeReference
(true) { + }, new TypeReference() { + })); + + public static final Event WITHDRAWALINITIATED_EVENT = new Event("WithdrawalInitiated", + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); + + @Deprecated + protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected TangemPaymentAccount(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected TangemPaymentAccount(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected TangemPaymentAccount(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static List getAccountStateAfterSettlementEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + AccountStateAfterSettlementEventResponse typedResponse = new AccountStateAfterSettlementEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.balance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.blockedAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.debtTotal = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.pendingRefundTotal = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static AccountStateAfterSettlementEventResponse getAccountStateAfterSettlementEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(ACCOUNTSTATEAFTERSETTLEMENT_EVENT, log); + AccountStateAfterSettlementEventResponse typedResponse = new AccountStateAfterSettlementEventResponse(); + typedResponse.log = log; + typedResponse.balance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.blockedAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.debtTotal = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.pendingRefundTotal = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); + return typedResponse; + } + + public static List getCardSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(CARDSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + CardSetEventResponse typedResponse = new CardSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getDebtIncreasedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + DebtIncreasedEventResponse typedResponse = new DebtIncreasedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getDebtPaidEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTPAID_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + DebtPaidEventResponse typedResponse = new DebtPaidEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.paid = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static CardSetEventResponse getCardSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(CARDSET_EVENT, log); + CardSetEventResponse typedResponse = new CardSetEventResponse(); + typedResponse.log = log; + typedResponse.card = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + return typedResponse; + } + + public static List getDebtWrittenOffEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + DebtWrittenOffEventResponse typedResponse = new DebtWrittenOffEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.writtenOff = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getInitializedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(INITIALIZED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + InitializedEventResponse typedResponse = new InitializedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getInsufficientFundsOnForcedAuthEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static DebtIncreasedEventResponse getDebtIncreasedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTINCREASED_EVENT, log); + DebtIncreasedEventResponse typedResponse = new DebtIncreasedEventResponse(); + typedResponse.log = log; + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static List getLimitsSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(LIMITSSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.singleTransactionLimit = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.spendLimit = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.noOtpSpendLimit = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.spendLimitsPeriod = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getNoOtpTransactionAuthorizedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(NOOTPTRANSACTIONAUTHORIZED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + NoOtpTransactionAuthorizedEventResponse typedResponse = new NoOtpTransactionAuthorizedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getOtpStateSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static DebtPaidEventResponse getDebtPaidEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTPAID_EVENT, log); + DebtPaidEventResponse typedResponse = new DebtPaidEventResponse(); + typedResponse.log = log; + typedResponse.paid = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static List getOwnerWalletSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(OWNERWALLETSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + OwnerWalletSetEventResponse typedResponse = new OwnerWalletSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.ownerWallet = (String) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getPendingRefundSavedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PENDINGREFUNDSAVED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + PendingRefundSavedEventResponse typedResponse = new PendingRefundSavedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getProcessorSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + ProcessorSetEventResponse typedResponse = new ProcessorSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.processor = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentToken = (String) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static DebtWrittenOffEventResponse getDebtWrittenOffEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(DEBTWRITTENOFF_EVENT, log); + DebtWrittenOffEventResponse typedResponse = new DebtWrittenOffEventResponse(); + typedResponse.log = log; + typedResponse.writtenOff = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.debtLeft = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static List getRefundPaidEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + RefundPaidEventResponse typedResponse = new RefundPaidEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getTransactionAmountChangedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + TransactionAmountChangedEventResponse typedResponse = new TransactionAmountChangedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.newAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getTransactionAuthorizedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + TransactionAuthorizedEventResponse typedResponse = new TransactionAuthorizedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otp = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static InitializedEventResponse getInitializedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INITIALIZED_EVENT, log); + InitializedEventResponse typedResponse = new InitializedEventResponse(); + typedResponse.log = log; + typedResponse.version = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getTransactionSettledEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + TransactionSettledEventResponse typedResponse = new TransactionSettledEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.settlementId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.settlementAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getUnsettledTransactionUnblockedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + UnsettledTransactionUnblockedEventResponse typedResponse = new UnsettledTransactionUnblockedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getUpgradedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(UPGRADED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + UpgradedEventResponse typedResponse = new UpgradedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static InsufficientFundsOnForcedAuthEventResponse getInsufficientFundsOnForcedAuthEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT, log); + InsufficientFundsOnForcedAuthEventResponse typedResponse = new InsufficientFundsOnForcedAuthEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.insufficientAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static List getVerifiedBalanceIncreasedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + VerifiedBalanceIncreasedEventResponse typedResponse = new VerifiedBalanceIncreasedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getVerifiedBalanceSetEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + VerifiedBalanceSetEventResponse typedResponse = new VerifiedBalanceSetEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getWithdrawalCanceledEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + WithdrawalCanceledEventResponse typedResponse = new WithdrawalCanceledEventResponse(); + typedResponse.log = eventValues.getLog(); + responses.add(typedResponse); + } + return responses; + } + + public static LimitsSetEventResponse getLimitsSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(LIMITSSET_EVENT, log); + LimitsSetEventResponse typedResponse = new LimitsSetEventResponse(); + typedResponse.log = log; + typedResponse.singleTransactionLimit = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.spendLimit = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.noOtpSpendLimit = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + typedResponse.spendLimitsPeriod = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue(); + return typedResponse; + } + + public static List getWithdrawalCompleteEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + WithdrawalCompleteEventResponse typedResponse = new WithdrawalCompleteEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + responses.add(typedResponse); + } + return responses; + } + + public static List getWithdrawalInitiatedEvents(TransactionReceipt transactionReceipt) { + List valueList = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, transactionReceipt); + ArrayList responses = new ArrayList(valueList.size()); + for (EventValuesWithLog eventValues : valueList) { + WithdrawalInitiatedEventResponse typedResponse = new WithdrawalInitiatedEventResponse(); + typedResponse.log = eventValues.getLog(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.readyToWithdrawTimestamp = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + responses.add(typedResponse); + } + return responses; + } + + @Deprecated + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccount(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + public static NoOtpTransactionAuthorizedEventResponse getNoOtpTransactionAuthorizedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(NOOTPTRANSACTIONAUTHORIZED_EVENT, log); + NoOtpTransactionAuthorizedEventResponse typedResponse = new NoOtpTransactionAuthorizedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + @Deprecated + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new TangemPaymentAccount(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccount(contractAddress, web3j, credentials, contractGasProvider); + } + + public static TangemPaymentAccount load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new TangemPaymentAccount(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static OtpStateSetEventResponse getOtpStateSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OTPSTATESET_EVENT, log); + OtpStateSetEventResponse typedResponse = new OtpStateSetEventResponse(); + typedResponse.log = log; + typedResponse.otpRoot = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + public static OwnerWalletSetEventResponse getOwnerWalletSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(OWNERWALLETSET_EVENT, log); + OwnerWalletSetEventResponse typedResponse = new OwnerWalletSetEventResponse(); + typedResponse.log = log; + typedResponse.ownerWallet = (String) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String trustedForwarder) { + String encodedConstructor = FunctionEncoder.encodeConstructor(List.of(new Address(160, trustedForwarder))); + return deployRemoteCall(TangemPaymentAccount.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + public static PendingRefundSavedEventResponse getPendingRefundSavedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PENDINGREFUNDSAVED_EVENT, log); + PendingRefundSavedEventResponse typedResponse = new PendingRefundSavedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static ProcessorSetEventResponse getProcessorSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(PROCESSORSET_EVENT, log); + ProcessorSetEventResponse typedResponse = new ProcessorSetEventResponse(); + typedResponse.log = log; + typedResponse.processor = (String) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentToken = (String) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static RefundPaidEventResponse getRefundPaidEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(REFUNDPAID_EVENT, log); + RefundPaidEventResponse typedResponse = new RefundPaidEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static TransactionAmountChangedEventResponse getTransactionAmountChangedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAMOUNTCHANGED_EVENT, log); + TransactionAmountChangedEventResponse typedResponse = new TransactionAmountChangedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.newAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static TransactionAuthorizedEventResponse getTransactionAuthorizedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONAUTHORIZED_EVENT, log); + TransactionAuthorizedEventResponse typedResponse = new TransactionAuthorizedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.otp = (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + typedResponse.otpCounter = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue(); + return typedResponse; + } + + public static TransactionSettledEventResponse getTransactionSettledEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSACTIONSETTLED_EVENT, log); + TransactionSettledEventResponse typedResponse = new TransactionSettledEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.settlementId = (BigInteger) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.settlementAmount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.paymentAmount = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static UnsettledTransactionUnblockedEventResponse getUnsettledTransactionUnblockedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT, log); + UnsettledTransactionUnblockedEventResponse typedResponse = new UnsettledTransactionUnblockedEventResponse(); + typedResponse.log = log; + typedResponse.transactionId = (BigInteger) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static UpgradedEventResponse getUpgradedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(UPGRADED_EVENT, log); + UpgradedEventResponse typedResponse = new UpgradedEventResponse(); + typedResponse.log = log; + typedResponse.implementation = (String) eventValues.getIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static VerifiedBalanceIncreasedEventResponse getVerifiedBalanceIncreasedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCEINCREASED_EVENT, log); + VerifiedBalanceIncreasedEventResponse typedResponse = new VerifiedBalanceIncreasedEventResponse(); + typedResponse.log = log; + typedResponse.increase = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public static VerifiedBalanceSetEventResponse getVerifiedBalanceSetEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(VERIFIEDBALANCESET_EVENT, log); + VerifiedBalanceSetEventResponse typedResponse = new VerifiedBalanceSetEventResponse(); + typedResponse.log = log; + typedResponse.verifiedBalance = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static WithdrawalCanceledEventResponse getWithdrawalCanceledEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCANCELED_EVENT, log); + WithdrawalCanceledEventResponse typedResponse = new WithdrawalCanceledEventResponse(); + typedResponse.log = log; + return typedResponse; + } + + public static WithdrawalCompleteEventResponse getWithdrawalCompleteEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALCOMPLETE_EVENT, log); + WithdrawalCompleteEventResponse typedResponse = new WithdrawalCompleteEventResponse(); + typedResponse.log = log; + typedResponse.to = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + return typedResponse; + } + + public static WithdrawalInitiatedEventResponse getWithdrawalInitiatedEventFromLog(Log log) { + EventValuesWithLog eventValues = staticExtractEventParametersWithLog(WITHDRAWALINITIATED_EVENT, log); + WithdrawalInitiatedEventResponse typedResponse = new WithdrawalInitiatedEventResponse(); + typedResponse.log = log; + typedResponse.amount = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + typedResponse.readyToWithdrawTimestamp = (BigInteger) eventValues.getNonIndexedValues().get(1).getValue(); + return typedResponse; + } + + public Flowable accountStateAfterSettlementEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getAccountStateAfterSettlementEventFromLog(log)); + } + + public Flowable accountStateAfterSettlementEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(ACCOUNTSTATEAFTERSETTLEMENT_EVENT)); + return accountStateAfterSettlementEventFlowable(filter); + } + + public Flowable cardSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getCardSetEventFromLog(log)); + } + + public Flowable cardSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(CARDSET_EVENT)); + return cardSetEventFlowable(filter); + } + + public Flowable debtIncreasedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtIncreasedEventFromLog(log)); + } + + public Flowable debtIncreasedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTINCREASED_EVENT)); + return debtIncreasedEventFlowable(filter); + } + + public Flowable debtPaidEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtPaidEventFromLog(log)); + } + + public Flowable debtPaidEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTPAID_EVENT)); + return debtPaidEventFlowable(filter); + } + + public Flowable debtWrittenOffEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getDebtWrittenOffEventFromLog(log)); + } + + public Flowable debtWrittenOffEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(DEBTWRITTENOFF_EVENT)); + return debtWrittenOffEventFlowable(filter); + } + + public Flowable initializedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getInitializedEventFromLog(log)); + } + + public Flowable initializedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(INITIALIZED_EVENT)); + return initializedEventFlowable(filter); + } + + public Flowable insufficientFundsOnForcedAuthEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getInsufficientFundsOnForcedAuthEventFromLog(log)); + } + + public Flowable insufficientFundsOnForcedAuthEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(INSUFFICIENTFUNDSONFORCEDAUTH_EVENT)); + return insufficientFundsOnForcedAuthEventFlowable(filter); + } + + public Flowable limitsSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getLimitsSetEventFromLog(log)); + } + + public Flowable limitsSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(LIMITSSET_EVENT)); + return limitsSetEventFlowable(filter); + } + + public Flowable noOtpTransactionAuthorizedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getNoOtpTransactionAuthorizedEventFromLog(log)); + } + + public Flowable noOtpTransactionAuthorizedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(NOOTPTRANSACTIONAUTHORIZED_EVENT)); + return noOtpTransactionAuthorizedEventFlowable(filter); + } + + public Flowable otpStateSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOtpStateSetEventFromLog(log)); + } + + public Flowable otpStateSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(OTPSTATESET_EVENT)); + return otpStateSetEventFlowable(filter); + } + + public Flowable ownerWalletSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getOwnerWalletSetEventFromLog(log)); + } + + public Flowable ownerWalletSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(OWNERWALLETSET_EVENT)); + return ownerWalletSetEventFlowable(filter); + } + + public Flowable pendingRefundSavedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getPendingRefundSavedEventFromLog(log)); + } + + public Flowable pendingRefundSavedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PENDINGREFUNDSAVED_EVENT)); + return pendingRefundSavedEventFlowable(filter); + } + + public Flowable processorSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getProcessorSetEventFromLog(log)); + } + + public Flowable processorSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(PROCESSORSET_EVENT)); + return processorSetEventFlowable(filter); + } + + public Flowable refundPaidEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getRefundPaidEventFromLog(log)); + } + + public Flowable refundPaidEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(REFUNDPAID_EVENT)); + return refundPaidEventFlowable(filter); + } + + public Flowable transactionAmountChangedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAmountChangedEventFromLog(log)); + } + + public Flowable transactionAmountChangedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAMOUNTCHANGED_EVENT)); + return transactionAmountChangedEventFlowable(filter); + } + + public Flowable transactionAuthorizedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionAuthorizedEventFromLog(log)); + } + + public Flowable transactionAuthorizedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONAUTHORIZED_EVENT)); + return transactionAuthorizedEventFlowable(filter); + } + + public Flowable transactionSettledEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getTransactionSettledEventFromLog(log)); + } + + public Flowable transactionSettledEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(TRANSACTIONSETTLED_EVENT)); + return transactionSettledEventFlowable(filter); + } + + public Flowable unsettledTransactionUnblockedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUnsettledTransactionUnblockedEventFromLog(log)); + } + + public Flowable unsettledTransactionUnblockedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(UNSETTLEDTRANSACTIONUNBLOCKED_EVENT)); + return unsettledTransactionUnblockedEventFlowable(filter); + } + + public Flowable upgradedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getUpgradedEventFromLog(log)); + } + + public Flowable upgradedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(UPGRADED_EVENT)); + return upgradedEventFlowable(filter); + } + + public Flowable withdrawalInitiatedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalInitiatedEventFromLog(log)); + } + + public Flowable withdrawalInitiatedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALINITIATED_EVENT)); + return withdrawalInitiatedEventFlowable(filter); + } + + public Flowable verifiedBalanceIncreasedEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceIncreasedEventFromLog(log)); + } + + public Flowable verifiedBalanceIncreasedEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCEINCREASED_EVENT)); + return verifiedBalanceIncreasedEventFlowable(filter); + } + + public Flowable verifiedBalanceSetEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getVerifiedBalanceSetEventFromLog(log)); + } + + public Flowable verifiedBalanceSetEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(VERIFIEDBALANCESET_EVENT)); + return verifiedBalanceSetEventFlowable(filter); + } + + public Flowable withdrawalCanceledEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCanceledEventFromLog(log)); + } + + public Flowable withdrawalCanceledEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCANCELED_EVENT)); + return withdrawalCanceledEventFlowable(filter); + } + + public Flowable withdrawalCompleteEventFlowable(EthFilter filter) { + return web3j.ethLogFlowable(filter).map(log -> getWithdrawalCompleteEventFromLog(log)); + } + + public Flowable withdrawalCompleteEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { + EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); + filter.addSingleTopic(EventEncoder.encode(WITHDRAWALCOMPLETE_EVENT)); + return withdrawalCompleteEventFlowable(filter); + } + + public RemoteFunctionCall UPGRADE_INTERFACE_VERSION() { + final Function function = new Function(FUNC_UPGRADE_INTERFACE_VERSION, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall> authorizedTransactions(BigInteger param0) { + final Function function = new Function(FUNC_AUTHORIZEDTRANSACTIONS, + List.of(new Uint256(param0)), + Arrays.asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + return new RemoteFunctionCall>(function, + new Callable>() { + @Override + public Tuple3 call() throws Exception { + List results = executeCallMultipleValueReturn(function); + return new Tuple3( + (BigInteger) results.get(0).getValue(), + (BigInteger) results.get(1).getValue(), + (Boolean) results.get(2).getValue()); + } + }); + } + + public RemoteFunctionCall availableForDebtPayment() { + final Function function = new Function(FUNC_AVAILABLEFORDEBTPAYMENT, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall availableForPayment() { + final Function function = new Function(FUNC_AVAILABLEFORPAYMENT, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall availableForWithdrawal() { + final Function function = new Function(FUNC_AVAILABLEFORWITHDRAWAL, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall blockedAmount() { + final Function function = new Function(FUNC_BLOCKEDAMOUNT, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall cancelWithdrawal() { + final Function function = new Function( + FUNC_CANCELWITHDRAWAL, + List.of(), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall> cardWithOtp() { + final Function function = new Function(FUNC_CARDWITHOTP, + List.of(), + Arrays.asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + return new RemoteFunctionCall>(function, + new Callable>() { + @Override + public Tuple3 call() throws Exception { + List results = executeCallMultipleValueReturn(function); + return new Tuple3( + (CardWithOtp) results.get(0), + (CardWithOtp) results.get(1), + (BigInteger) results.get(2).getValue()); + } + }); + } + + public RemoteFunctionCall debtAmount() { + final Function function = new Function(FUNC_DEBTAMOUNT, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall factory() { + final Function function = new Function(FUNC_FACTORY, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall increaseVerifiedBalance(BigInteger increase) { + final Function function = new Function( + FUNC_INCREASEVERIFIEDBALANCE, + List.of(new Uint256(increase)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall initWithdrawal(BigInteger amount) { + final Function function = new Function( + FUNC_INITWITHDRAWAL, + List.of(new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall initialize(String ownerWallet_, BigInteger singleTransactionLimit, BigInteger spendLimit, BigInteger noOtpSpendLimit, BigInteger spendLimitsPeriod) { + final Function function = new Function( + FUNC_INITIALIZE, + Arrays.asList(new Address(160, ownerWallet_), + new Uint256(singleTransactionLimit), + new Uint256(spendLimit), + new Uint256(noOtpSpendLimit), + new Uint256(spendLimitsPeriod)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall isTrustedForwarder(String forwarder) { + final Function function = new Function(FUNC_ISTRUSTEDFORWARDER, + List.of(new Address(160, forwarder)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall isWithdrawalInProgress() { + final Function function = new Function(FUNC_ISWITHDRAWALINPROGRESS, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall isWithdrawalReady() { + final Function function = new Function(FUNC_ISWITHDRAWALREADY, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, Boolean.class); + } + + public RemoteFunctionCall> limits() { + final Function function = new Function(FUNC_LIMITS, + List.of(), + Arrays.asList(new TypeReference() { + }, new TypeReference() { + }, new TypeReference() { + })); + return new RemoteFunctionCall>(function, + new Callable>() { + @Override + public Tuple3 call() throws Exception { + List results = executeCallMultipleValueReturn(function); + return new Tuple3( + (Limits) results.get(0), + (Limits) results.get(1), + (BigInteger) results.get(2).getValue()); + } + }); + } + + public RemoteFunctionCall ownerWallet() { + final Function function = new Function(FUNC_OWNERWALLET, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall paymentToken() { + final Function function = new Function(FUNC_PAYMENTTOKEN, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall pendingRefundAmount(BigInteger transactionId) { + final Function function = new Function(FUNC_PENDINGREFUNDAMOUNT, + List.of(new Uint256(transactionId)), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall pendingRefundTotal() { + final Function function = new Function(FUNC_PENDINGREFUNDTOTAL, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall processAuthorization(BigInteger transactionId, BigInteger amount, byte[] otp, BigInteger otpCounter, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATION, + Arrays.asList(new Uint256(transactionId), + new Uint256(amount), + new Bytes16(otp), + new Uint16(otpCounter), + new Bool(forced)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processAuthorizationChange(BigInteger transactionId, BigInteger newAmount) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATIONCHANGE, + Arrays.asList(new Uint256(transactionId), + new Uint256(newAmount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processAuthorizationNoOtp(BigInteger transactionId, BigInteger amount, Boolean forced) { + final Function function = new Function( + FUNC_PROCESSAUTHORIZATIONNOOTP, + Arrays.asList(new Uint256(transactionId), + new Uint256(amount), + new Bool(forced)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processDebt(BigInteger amount) { + final Function function = new Function( + FUNC_PROCESSDEBT, + List.of(new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processPendingRefundPayment(BigInteger transactionId) { + final Function function = new Function( + FUNC_PROCESSPENDINGREFUNDPAYMENT, + List.of(new Uint256(transactionId)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processRefundPayment(BigInteger transactionId) { + final Function function = new Function( + FUNC_PROCESSREFUNDPAYMENT, + List.of(new Uint256(transactionId)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processSettlement(BigInteger transactionId, BigInteger settlementId, BigInteger amount) { + final Function function = new Function( + FUNC_PROCESSSETTLEMENT, + Arrays.asList(new Uint256(transactionId), + new Uint256(settlementId), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processWithdrawal(String to) { + final Function function = new Function( + FUNC_PROCESSWITHDRAWAL, + List.of(new Address(160, to)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall processor() { + final Function function = new Function(FUNC_PROCESSOR, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall proxiableUUID() { + final Function function = new Function(FUNC_PROXIABLEUUID, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, byte[].class); + } + + public RemoteFunctionCall savePendingRefund(BigInteger transactionId, BigInteger amount) { + final Function function = new Function( + FUNC_SAVEPENDINGREFUND, + Arrays.asList(new Uint256(transactionId), + new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall securityDelay() { + final Function function = new Function(FUNC_SECURITYDELAY, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall setCard(String card, byte[] otpRoot, BigInteger otpCounter) { + final Function function = new Function( + FUNC_SETCARD, + Arrays.asList(new Address(160, card), + new Bytes16(otpRoot), + new Uint16(otpCounter)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setLimits(BigInteger singleTransactionLimit, BigInteger spendLimit, BigInteger noOtpSpendLimit, BigInteger spendLimitsPeriod) { + final Function function = new Function( + FUNC_SETLIMITS, + Arrays.asList(new Uint256(singleTransactionLimit), + new Uint256(spendLimit), + new Uint256(noOtpSpendLimit), + new Uint256(spendLimitsPeriod)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setOtpState(byte[] otp, BigInteger counter) { + final Function function = new Function( + FUNC_SETOTPSTATE, + Arrays.asList(new Bytes16(otp), + new Uint16(counter)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setOwnerWallet(String ownerWallet_) { + final Function function = new Function( + FUNC_SETOWNERWALLET, + List.of(new Address(160, ownerWallet_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setProcessor(String processor_) { + final Function function = new Function( + FUNC_SETPROCESSOR, + List.of(new Address(160, processor_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall setVerifiedBalance(BigInteger verifiedBalance_) { + final Function function = new Function( + FUNC_SETVERIFIEDBALANCE, + List.of(new Uint256(verifiedBalance_)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall settlementPeriod() { + final Function function = new Function(FUNC_SETTLEMENTPERIOD, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall trustedForwarder() { + final Function function = new Function(FUNC_TRUSTEDFORWARDER, + List.of(), + List.of(new TypeReference
() { + })); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall unblockUnsettledTransaction(BigInteger transactionId) { + final Function function = new Function( + FUNC_UNBLOCKUNSETTLEDTRANSACTION, + List.of(new Uint256(transactionId)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall upgradeToAndCall(String newImplementation, byte[] data, BigInteger weiValue) { + final Function function = new Function( + FUNC_UPGRADETOANDCALL, + Arrays.asList(new Address(160, newImplementation), + new org.web3j.abi.datatypes.DynamicBytes(data)), + Collections.emptyList()); + return executeRemoteCallTransaction(function, weiValue); + } + + public RemoteFunctionCall verifiedBalance() { + final Function function = new Function(FUNC_VERIFIEDBALANCE, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall withdrawalAmount() { + final Function function = new Function(FUNC_WITHDRAWALAMOUNT, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall withdrawalReadyTimestamp() { + final Function function = new Function(FUNC_WITHDRAWALREADYTIMESTAMP, + List.of(), + List.of(new TypeReference() { + })); + return executeRemoteCallSingleValueReturn(function, BigInteger.class); + } + + public RemoteFunctionCall writeOffDebt(BigInteger amount) { + final Function function = new Function( + FUNC_WRITEOFFDEBT, + List.of(new Uint256(amount)), + Collections.emptyList()); + return executeRemoteCallTransaction(function); + } + + public static class OtpState extends StaticStruct { + public byte[] otp; + + public BigInteger counter; + + public OtpState(byte[] otp, BigInteger counter) { + super(new Bytes16(otp), + new Uint16(counter)); + this.otp = otp; + this.counter = counter; + } + + public OtpState(Bytes16 otp, Uint16 counter) { + super(otp, counter); + this.otp = otp.getValue(); + this.counter = counter.getValue(); + } + } + + public static class Limit extends StaticStruct { + public BigInteger _00_limit; + + public BigInteger _01_spent; + + public Limit(BigInteger limit, BigInteger spent) { + super(new Uint256(limit), + new Uint256(spent)); + this._00_limit = limit; + this._01_spent = spent; + } + + public Limit(Uint256 limit, Uint256 spent) { + super(limit, spent); + this._00_limit = limit.getValue(); + this._01_spent = spent.getValue(); + } + } + + public static class Timer extends StaticStruct { + public BigInteger expireTimestamp; + + public Timer(BigInteger expireTimestamp) { + super(new Uint256(expireTimestamp)); + this.expireTimestamp = expireTimestamp; + } + + public Timer(Uint256 expireTimestamp) { + super(expireTimestamp); + this.expireTimestamp = expireTimestamp.getValue(); + } + } + + public static class CardWithOtp extends StaticStruct { + public String card; + + public OtpState otpState; + + public CardWithOtp(String card, OtpState otpState) { + super(new Address(160, card), + otpState); + this.card = card; + this.otpState = otpState; + } + + public CardWithOtp(Address card, OtpState otpState) { + super(card, otpState); + this.card = card.getValue(); + this.otpState = otpState; + } + } + + public static class Limits extends StaticStruct { + public BigInteger _00_singleTransactionLimit; + + public Limit _01_spendLimit; + + public Limit _02_noOtpSpendLimit; + + public Timer _03_spendLimitsTimer; + + public BigInteger _04_spendLimitsPeriod; + + public Limits(BigInteger singleTransactionLimit, Limit spendLimit, Limit noOtpSpendLimit, Timer spendLimitsTimer, BigInteger spendLimitsPeriod) { + super(new Uint256(singleTransactionLimit), + spendLimit, + noOtpSpendLimit, + spendLimitsTimer, + new Uint256(spendLimitsPeriod)); + this._00_singleTransactionLimit = singleTransactionLimit; + this._01_spendLimit = spendLimit; + this._02_noOtpSpendLimit = noOtpSpendLimit; + this._03_spendLimitsTimer = spendLimitsTimer; + this._04_spendLimitsPeriod = spendLimitsPeriod; + } + + public Limits(Uint256 singleTransactionLimit, Limit spendLimit, Limit noOtpSpendLimit, Timer spendLimitsTimer, Uint256 spendLimitsPeriod) { + super(singleTransactionLimit, spendLimit, noOtpSpendLimit, spendLimitsTimer, spendLimitsPeriod); + this._00_singleTransactionLimit = singleTransactionLimit.getValue(); + this._01_spendLimit = spendLimit; + this._02_noOtpSpendLimit = noOtpSpendLimit; + this._03_spendLimitsTimer = spendLimitsTimer; + this._04_spendLimitsPeriod = spendLimitsPeriod.getValue(); + } + } + + public static class AccountStateAfterSettlementEventResponse extends BaseEventResponse { + public BigInteger balance; + + public BigInteger blockedAmount; + + public BigInteger debtTotal; + + public BigInteger pendingRefundTotal; + } + + public static class CardSetEventResponse extends BaseEventResponse { + public String card; + + public byte[] otpRoot; + + public BigInteger otpCounter; + } + + public static class DebtIncreasedEventResponse extends BaseEventResponse { + public BigInteger increase; + + public BigInteger debtAmount; + } + + public static class DebtPaidEventResponse extends BaseEventResponse { + public BigInteger paid; + + public BigInteger debtLeft; + } + + public static class DebtWrittenOffEventResponse extends BaseEventResponse { + public BigInteger writtenOff; + + public BigInteger debtLeft; + } + + public static class InitializedEventResponse extends BaseEventResponse { + public BigInteger version; + } + + public static class InsufficientFundsOnForcedAuthEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger insufficientAmount; + } + + public static class LimitsSetEventResponse extends BaseEventResponse { + public BigInteger singleTransactionLimit; + + public BigInteger spendLimit; + + public BigInteger noOtpSpendLimit; + + public BigInteger spendLimitsPeriod; + } + + public static class NoOtpTransactionAuthorizedEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class OtpStateSetEventResponse extends BaseEventResponse { + public byte[] otpRoot; + + public BigInteger otpCounter; + } + + public static class OwnerWalletSetEventResponse extends BaseEventResponse { + public String ownerWallet; + } + + public static class PendingRefundSavedEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class ProcessorSetEventResponse extends BaseEventResponse { + public String processor; + + public String paymentToken; + } + + public static class RefundPaidEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class TransactionAmountChangedEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger newAmount; + } + + public static class TransactionAuthorizedEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger amount; + + public byte[] otp; + + public BigInteger otpCounter; + } + + public static class TransactionSettledEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger settlementId; + + public BigInteger settlementAmount; + + public BigInteger paymentAmount; + } + + public static class UnsettledTransactionUnblockedEventResponse extends BaseEventResponse { + public BigInteger transactionId; + + public BigInteger amount; + } + + public static class UpgradedEventResponse extends BaseEventResponse { + public String implementation; + } + + public static class VerifiedBalanceIncreasedEventResponse extends BaseEventResponse { + public BigInteger increase; + + public BigInteger verifiedBalance; + } + + public static class VerifiedBalanceSetEventResponse extends BaseEventResponse { + public BigInteger verifiedBalance; + } + + public static class WithdrawalCanceledEventResponse extends BaseEventResponse { + } + + public static class WithdrawalCompleteEventResponse extends BaseEventResponse { + public String to; + + public BigInteger amount; + } + + public static class WithdrawalInitiatedEventResponse extends BaseEventResponse { + public BigInteger amount; + + public BigInteger readyToWithdrawTimestamp; + } +} diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt new file mode 100644 index 0000000000..c2ac96c707 --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt @@ -0,0 +1,141 @@ +package com.tangem.lib.visa + +import arrow.fx.coroutines.parZip +import com.tangem.lib.visa.model.VisaBalancesAndLimits +import com.tangem.lib.visa.model.VisaBalancesAndLimits.Balances +import com.tangem.lib.visa.model.VisaBalancesAndLimits.Limits +import com.tangem.lib.visa.utils.toBigDecimal +import com.tangem.lib.visa.utils.toInstant +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import org.joda.time.Instant +import org.web3j.protocol.Web3j +import org.web3j.tx.TransactionManager +import org.web3j.tx.gas.ContractGasProvider + +internal class DefaultVisaContractInfoProvider( + private val web3j: Web3j, + private val transactionManager: TransactionManager, + private val gasProvider: ContractGasProvider, + private val bridgeProcessorAddress: String, + private val dispatchers: CoroutineDispatcherProvider, +) : VisaContractInfoProvider { + + override suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits { + return withContext(dispatchers.io) { + val tangemBridgeProcessor = TangemBridgeProcessor.load( + /* contractAddress = */ bridgeProcessorAddress, + /* web3j = */ web3j, + /* transactionManager = */ transactionManager, + /* contractGasProvider = */ gasProvider, + ) + + parZip( + dispatchers.io, + { loadPaymentAccount(walletAddress, tangemBridgeProcessor) }, + { loadPaymentTokenInfo(tangemBridgeProcessor) }, + { paymentAccount, paymentToken -> + fetchBalancesAndLimits(paymentAccount, paymentToken) + }, + ) + } + } + + private fun loadPaymentAccount( + walletAddress: String, + tangemBridgeProcessor: TangemBridgeProcessor, + ): TangemPaymentAccount { + val paymentAccountAddress = tangemBridgeProcessor.getPaymentAccount(walletAddress).send() + + return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider) + } + + private fun loadPaymentTokenInfo(tangemBridgeProcessor: TangemBridgeProcessor): PaymentTokenInfo { + val paymentTokenContractAddress = tangemBridgeProcessor.paymentToken().send() + val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider) + val paymentTokenDecimals = paymentTokenContract.decimals().send() + + return PaymentTokenInfo( + decimals = paymentTokenDecimals.toInt(), + contract = paymentTokenContract, + ) + } + + private suspend fun fetchBalancesAndLimits( + paymentAccount: TangemPaymentAccount, + paymentToken: PaymentTokenInfo, + ): VisaBalancesAndLimits = parZip( + dispatchers.io, + { fetchBalances(paymentAccount, paymentToken) }, + { fetchLimits(paymentAccount, paymentToken) }, + { balances, (oldLimit, newLimit, changeDate) -> + VisaBalancesAndLimits(balances, oldLimit, newLimit, changeDate) + }, + ) + + private suspend fun fetchBalances(paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo): Balances { + return parZip( + dispatchers.io, + { paymentToken.contract.balanceOf(paymentAccount.contractAddress).send() }, + { paymentAccount.verifiedBalance().send() }, + { paymentAccount.availableForPayment().send() }, + { paymentAccount.availableForWithdrawal().send() }, + { paymentAccount.availableForDebtPayment().send() }, + { paymentAccount.blockedAmount().send() }, + { paymentAccount.debtAmount().send() }, + { paymentAccount.pendingRefundTotal().send() }, + ) { total, verified, payment, withdrawal, debtPayment, blocked, debt, refund -> + val decimals = paymentToken.decimals + + Balances( + total = total.toBigDecimal(decimals), + verified = verified.toBigDecimal(decimals), + available = Balances.Available( + forPayment = payment.toBigDecimal(decimals), + forWithdrawal = withdrawal.toBigDecimal(decimals), + forDebtPayment = debtPayment.toBigDecimal(decimals), + ), + blocked = blocked.toBigDecimal(decimals), + debt = debt.toBigDecimal(decimals), + pendingRefund = refund.toBigDecimal(decimals), + ) + } + } + + private fun fetchLimits( + paymentAccount: TangemPaymentAccount, + paymentToken: PaymentTokenInfo, + ): Triple { + val ( + oldLimit, + newLimit, + changeDateSeconds, + ) = paymentAccount.limits().send() + + return Triple( + first = getLimits(oldLimit, paymentToken), + second = getLimits(newLimit, paymentToken), + third = changeDateSeconds.toInstant(), + ) + } + + private fun getLimits(limit: TangemPaymentAccount.Limits, paymentToken: PaymentTokenInfo): Limits = Limits( + spendLimit = limit._01_spendLimit.toLimit(paymentToken.decimals), + noOtpLimit = limit._02_noOtpSpendLimit.toLimit(paymentToken.decimals), + singleTransactionLimit = limit._00_singleTransactionLimit.toBigDecimal(paymentToken.decimals), + expirationDate = limit._03_spendLimitsTimer.expireTimestamp.toInstant(), + spendPeriodSeconds = limit._04_spendLimitsPeriod, + ) + + private fun TangemPaymentAccount.Limit.toLimit(decimals: Int): Limits.Limit { + return Limits.Limit( + limit = _00_limit.toBigDecimal(decimals), + spent = _01_spent.toBigDecimal(decimals), + ) + } + + private data class PaymentTokenInfo( + val decimals: Int, + val contract: ERC20, + ) +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt new file mode 100644 index 0000000000..35a8a3c4ad --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt @@ -0,0 +1,90 @@ +package com.tangem.lib.visa + +import com.ihsanbal.logging.Level +import com.ihsanbal.logging.LoggingInterceptor +import com.tangem.lib.visa.model.VisaBalancesAndLimits +import com.tangem.lib.visa.utils.VisaConfig +import com.tangem.lib.visa.utils.VisaConfig.NETWORK_LOGS_TAG +import com.tangem.lib.visa.utils.toHexString +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import okhttp3.OkHttpClient +import org.web3j.crypto.Credentials +import org.web3j.protocol.Web3j +import org.web3j.protocol.http.HttpService +import org.web3j.tx.FastRawTransactionManager +import org.web3j.tx.TransactionManager +import org.web3j.tx.gas.ContractGasProvider +import org.web3j.tx.gas.StaticEIP1559GasProvider +import java.math.BigDecimal +import java.math.BigInteger +import java.util.concurrent.TimeUnit + +interface VisaContractInfoProvider { + + suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits + + class Builder( + private val isNetworkLoggingEnabled: Boolean, + private val dispatchers: CoroutineDispatcherProvider, + private val baseUrl: String = VisaConfig.BASE_RPC_URL, + private val bridgeProcessorAddress: String = VisaConfig.BRIDGE_PROCESSOR_CONTRACT_ADDRESS, + private val chainId: Long = VisaConfig.CHAIN_ID, + private val networkTimeoutSeconds: Long = VisaConfig.NETWORK_TIMEOUT_SECONDS, + private val decimals: Int = VisaConfig.DECIMALS, + private val gasLimit: Long = VisaConfig.GAS_LIMIT, + private val privateKey: String = ByteArray(VisaConfig.PRIVATE_KEY_LENGTH).toHexString(), + ) { + + fun build(): VisaContractInfoProvider { + val web3j = createWeb3J() + val gasProvider = createGasProvider() + val transactionManager = createTransactionManager(web3j) + + return DefaultVisaContractInfoProvider( + web3j = web3j, + transactionManager = transactionManager, + gasProvider = gasProvider, + bridgeProcessorAddress = bridgeProcessorAddress, + dispatchers = dispatchers, + ) + } + + private fun createWeb3J(): Web3j { + val httpClient = OkHttpClient.Builder().apply { + connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + + if (isNetworkLoggingEnabled) { + addInterceptor( + LoggingInterceptor.Builder() + .setLevel(Level.BODY) + .tag(NETWORK_LOGS_TAG) + .build(), + ) + } + }.build() + + val web3jService = HttpService( + /* url = */ baseUrl, + /* httpClient = */ httpClient, + /* includeRawResponses = */ false, + ) + + return Web3j.build(web3jService) + } + + private fun createGasProvider(): ContractGasProvider = StaticEIP1559GasProvider( + /* chainId = */ chainId, + /* maxFeePerGas = */ BigDecimal.ONE.movePointLeft(decimals).toBigInteger(), + /* maxPriorityFeePerGas = */ BigDecimal.ONE.movePointLeft(decimals).toBigInteger(), + /* gasLimit = */ BigInteger.valueOf(gasLimit), + ) + + private fun createTransactionManager(web3j: Web3j): TransactionManager = FastRawTransactionManager( + /* web3j = */ web3j, + /* credentials = */ Credentials.create(privateKey), + /* chainId = */ chainId, + ) + } +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt new file mode 100644 index 0000000000..b05b3096a2 --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt @@ -0,0 +1,16 @@ +package com.tangem.lib.visa.api + +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.lib.visa.model.VisaTxHistoryResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface VisaApi { + + @GET("transaction") + suspend fun getTxHistory( + @Query("card_public_key") cardPublicKey: String, + @Query("limit") limit: Int, + @Query("offset") offset: Int, + ): ApiResponse +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt new file mode 100644 index 0000000000..50f200249b --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt @@ -0,0 +1,61 @@ +package com.tangem.lib.visa.api + +import android.util.Log +import com.ihsanbal.logging.Level +import com.ihsanbal.logging.LoggingInterceptor +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory +import com.tangem.lib.visa.utils.VisaConfig +import com.tangem.lib.visa.utils.VisaConfig.NETWORK_LOGS_TAG +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import java.util.concurrent.TimeUnit + +class VisaApiBuilder( + private val useDevApi: Boolean, + private val isNetworkLoggingEnabled: Boolean, + private val moshi: Moshi, + private val networkTimeoutSeconds: Long = VisaConfig.NETWORK_TIMEOUT_SECONDS, +) { + + fun build(): VisaApi { + val okHttpClient = createOkHttpClient() + val retrofit = createRetrofit(okHttpClient) + return retrofit.create(VisaApi::class.java) + } + + private fun createOkHttpClient(): OkHttpClient { + val builder = OkHttpClient.Builder().apply { + connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) + + if (isNetworkLoggingEnabled) { + addInterceptor(createNetworkLoggingInterceptor()) + } + } + + return builder.build() + } + + private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit { + val baseUrl = if (useDevApi) VisaConfig.VISA_API_DEV_URL else VisaConfig.VISA_API_PROD_URL + + return Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) + .baseUrl(baseUrl) + .client(okHttpClient) + .build() + } +} + +private fun createNetworkLoggingInterceptor(): Interceptor { + return LoggingInterceptor.Builder() + .setLevel(Level.BODY) + .log(Log.VERBOSE) + .tag(NETWORK_LOGS_TAG) + .build() +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaBalancesAndLimits.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaBalancesAndLimits.kt new file mode 100644 index 0000000000..50145e05da --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaBalancesAndLimits.kt @@ -0,0 +1,43 @@ +package com.tangem.lib.visa.model + +import org.joda.time.Instant +import java.math.BigDecimal +import java.math.BigInteger + +data class VisaBalancesAndLimits( + val balances: Balances, + val oldLimits: Limits, + val newLimits: Limits, + val limitsChangeDate: Instant, +) { + + data class Balances( + val total: BigDecimal, + val verified: BigDecimal, + val available: Available, + val blocked: BigDecimal, + val debt: BigDecimal, + val pendingRefund: BigDecimal, + ) { + + data class Available( + val forPayment: BigDecimal, + val forWithdrawal: BigDecimal, + val forDebtPayment: BigDecimal, + ) + } + + data class Limits( + val spendLimit: Limit, + val noOtpLimit: Limit, + val singleTransactionLimit: BigDecimal, + val expirationDate: Instant, + val spendPeriodSeconds: BigInteger, + ) { + + data class Limit( + val limit: BigDecimal, + val spent: BigDecimal, + ) + } +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt new file mode 100644 index 0000000000..dcec060570 --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt @@ -0,0 +1,84 @@ +package com.tangem.lib.visa.model + +import com.squareup.moshi.Json +import org.joda.time.DateTime +import java.math.BigDecimal + +data class VisaTxHistoryResponse( + @Json(name = "card_wallet_address") + val cardWalletAddress: String, + @Json(name = "transactions") + val transactions: List, +) { + + data class Transaction( + @Json(name = "auth_code") + val authCode: String?, + @Json(name = "billing_amount") + val billingAmount: BigDecimal, + @Json(name = "billing_currency_code") + val billingCurrencyCode: Int, + @Json(name = "blockchain_amount") + val blockchainAmount: BigDecimal, + @Json(name = "blockchain_coin_name") + val blockchainCoinName: String, + @Json(name = "blockchain_fee") + val blockchainFee: BigDecimal, + // @Json(name = "local_dt") + // val localDate: DateTime?, + @Json(name = "merchant_category_code") + val merchantCategoryCode: String?, + @Json(name = "merchant_city") + val merchantCity: String?, + @Json(name = "merchant_country_code") + val merchantCountryCode: String?, + @Json(name = "merchant_name") + val merchantName: String?, + @Json(name = "requests") + val requests: List, + @Json(name = "rrn") + val rrn: String?, + @Json(name = "transaction_amount") + val transactionAmount: BigDecimal, + @Json(name = "transaction_currency_code") + val transactionCurrencyCode: Int, + @Json(name = "transaction_dt") + val transactionDt: DateTime, + @Json(name = "transaction_id") + val transactionId: Long, + @Json(name = "transaction_status") + val transactionStatus: String, + @Json(name = "transaction_type") + val transactionType: String, + ) { + + data class Request( + @Json(name = "billing_amount") + val billingAmount: BigDecimal, + @Json(name = "billing_currency_code") + val billingCurrencyCode: Int, + @Json(name = "blockchain_amount") + val blockchainAmount: BigDecimal, + @Json(name = "blockchain_fee") + val blockchainFee: BigDecimal, + @Json(name = "error_code") + val errorCode: Int, + @Json(name = "request_dt") + val requestDt: DateTime, + @Json(name = "request_status") + val requestStatus: String, + @Json(name = "request_type") + val requestType: String, + @Json(name = "transaction_amount") + val transactionAmount: BigDecimal, + @Json(name = "transaction_currency_code") + val transactionCurrencyCode: Int, + @Json(name = "transaction_request_id") + val transactionRequestId: Long, + @Json(name = "tx_hash") + val txHash: String?, + @Json(name = "tx_status") + val txStatus: String?, + ) + } +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt new file mode 100644 index 0000000000..4b129ed3c4 --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/BigIntegerExt.kt @@ -0,0 +1,13 @@ +package com.tangem.lib.visa.utils + +import org.joda.time.Instant +import java.math.BigDecimal +import java.math.BigInteger + +internal fun BigInteger.toBigDecimal(decimals: Int): BigDecimal { + return BigDecimal(this).movePointLeft(decimals) +} + +internal fun BigInteger.toInstant(): Instant { + return Instant.ofEpochSecond(toLong()) +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/ByteArrayExt.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/ByteArrayExt.kt new file mode 100644 index 0000000000..3efd2be245 --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/ByteArrayExt.kt @@ -0,0 +1,3 @@ +package com.tangem.lib.visa.utils + +internal fun ByteArray.toHexString(): String = joinToString("") { "%02X".format(it) } \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/VisaConfig.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/VisaConfig.kt new file mode 100644 index 0000000000..7819bd734e --- /dev/null +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/utils/VisaConfig.kt @@ -0,0 +1,17 @@ +package com.tangem.lib.visa.utils + +internal object VisaConfig { + + const val BASE_RPC_URL = "https://rpc-mumbai.maticvigil.com/" + const val BRIDGE_PROCESSOR_CONTRACT_ADDRESS = "0x62119697e78178512bfcc456ae6d1b7dee9fbaa6" + const val CHAIN_ID = 80_001L + const val DECIMALS = 9 + const val GAS_LIMIT = 500_000_000L + const val PRIVATE_KEY_LENGTH = 32 + + const val VISA_API_PROD_URL = "https://payapi.tangem-tech.com/api/v1/" + const val VISA_API_DEV_URL = "[REDACTED_ENV_URL]" + + const val NETWORK_TIMEOUT_SECONDS = 65L + const val NETWORK_LOGS_TAG = "VisaNetworkLogs" +} \ No newline at end of file diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt index 3504fee112..142649ae2a 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt @@ -23,6 +23,7 @@ internal fun BaseExtension.configureCompose(project: Project) { contains(Regex(pattern = ":onboarding\$")) || // TODO: divide on api/impl after migrating all onboarding to module contains(Regex(pattern = ":presentation\$")) || contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA] + contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function contains(Regex(pattern = ":impl\$")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 11c13bfbe1..19fcb80b60 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -77,6 +77,7 @@ include(":core:deep-links:global") // region Libs modules include(":libs:crypto") include(":libs:auth") +include(":libs:visa") // endregion Libs modules // region Feature modules